[PATCH V5 07/12] pinctrl: tegra-xusb: Add USB PHY support

Andrew Bresticker abrestic at chromium.org
Mon Nov 17 14:41:37 PST 2014


In addition to the PCIe and SATA PHYs, the XUSB pad controller also
supports 3 UTMI, 2 HSIC, and 2 USB3 PHYs.  Each USB3 PHY uses a single
PCIe or SATA lane and is mapped to one of the three UTMI ports.

The xHCI controller will also send messages intended for the PHY driver,
so request and listen for messages on the mailbox's PHY channel.

Signed-off-by: Andrew Bresticker <abrestic at chromium.org>
Acked-by: Linus Walleij <linus.walleij at linaro.org>
Reviewed-by: Stephen Warren <swarren at nvidia.com>
---
Changes from v4:
 - Disabled USB support on missing mailbox channel instead of failing
   to probe.
 - Made usb3-port a pinconfig property.
 - Addressed review comments from Thierry.
No changes from v3.
Changes from v2:
 - Added support for nvidia,otg-hs-curr-level-offset property.
 - Moved mailbox request handling to workqueue.
 - Added filtering out of non-PHY mailbox messages.
 - Dropped "-otg" from VBUS supplies.
Changes from v1:
 - Updated to use common mailbox API.
 - Added SATA PHY enable sequence for USB3 ports using the SATA lane.
 - Made USB3 port-to-lane mappins a top-level binding rather than a pinconfig
   binding.
---
 drivers/pinctrl/Kconfig              |    1 +
 drivers/pinctrl/pinctrl-tegra-xusb.c | 1262 +++++++++++++++++++++++++++++++++-
 include/soc/tegra/xusb.h             |    7 +
 3 files changed, 1254 insertions(+), 16 deletions(-)

diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index c6a66de..b2a96f3 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -160,6 +160,7 @@ config PINCTRL_TEGRA124
 
 config PINCTRL_TEGRA_XUSB
 	def_bool y if ARCH_TEGRA
+	depends on MAILBOX
 	select GENERIC_PHY
 	select PINCONF
 	select PINMUX
diff --git a/drivers/pinctrl/pinctrl-tegra-xusb.c b/drivers/pinctrl/pinctrl-tegra-xusb.c
index 1631ec9..37ad84e 100644
--- a/drivers/pinctrl/pinctrl-tegra-xusb.c
+++ b/drivers/pinctrl/pinctrl-tegra-xusb.c
@@ -13,23 +13,54 @@
 
 #include <linux/delay.h>
 #include <linux/io.h>
+#include <linux/mailbox_client.h>
 #include <linux/module.h>
 #include <linux/of.h>
 #include <linux/phy/phy.h>
 #include <linux/pinctrl/pinctrl.h>
 #include <linux/pinctrl/pinmux.h>
 #include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
 #include <linux/reset.h>
+#include <linux/workqueue.h>
+
+#include <soc/tegra/fuse.h>
+#include <soc/tegra/xusb.h>
 
 #include <dt-bindings/pinctrl/pinctrl-tegra-xusb.h>
 
 #include "core.h"
 #include "pinctrl-utils.h"
 
+#define FUSE_SKU_CALIB_HS_CURR_LEVEL_PADX_SHIFT(x) ((x) ? 15 : 0)
+#define FUSE_SKU_CALIB_HS_CURR_LEVEL_PAD_MASK 0x3f
+#define FUSE_SKU_CALIB_HS_IREF_CAP_SHIFT 13
+#define FUSE_SKU_CALIB_HS_IREF_CAP_MASK 0x3
+#define FUSE_SKU_CALIB_HS_SQUELCH_LEVEL_SHIFT 11
+#define FUSE_SKU_CALIB_HS_SQUELCH_LEVEL_MASK 0x3
+#define FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_SHIFT 7
+#define FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_MASK 0xf
+
+#define XUSB_PADCTL_USB2_PORT_CAP 0x008
+#define XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_SHIFT(x) ((x) * 4)
+#define XUSB_PADCTL_USB2_PORT_CAP_PORT_CAP_MASK 0x3
+#define XUSB_PADCTL_USB2_PORT_CAP_DISABLED 0x0
+#define XUSB_PADCTL_USB2_PORT_CAP_HOST 0x1
+#define XUSB_PADCTL_USB2_PORT_CAP_DEVICE 0x2
+#define XUSB_PADCTL_USB2_PORT_CAP_OTG 0x3
+
+#define XUSB_PADCTL_SS_PORT_MAP 0x014
+#define XUSB_PADCTL_SS_PORT_MAP_PORTX_SHIFT(x) ((x) * 4)
+#define XUSB_PADCTL_SS_PORT_MAP_PORT_MASK 0x7
+
 #define XUSB_PADCTL_ELPG_PROGRAM 0x01c
 #define XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_VCORE_DOWN (1 << 26)
 #define XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN_EARLY (1 << 25)
 #define XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN (1 << 24)
+#define XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_VCORE_DOWN(x) (1 << (18 + (x) * 4))
+#define XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_CLAMP_EN_EARLY(x) \
+							(1 << (17 + (x) * 4))
+#define XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_CLAMP_EN(x) (1 << (16 + (x) * 4))
 
 #define XUSB_PADCTL_IOPHY_PLL_P0_CTL1 0x040
 #define XUSB_PADCTL_IOPHY_PLL_P0_CTL1_PLL0_LOCKDET (1 << 19)
@@ -41,17 +72,136 @@
 #define XUSB_PADCTL_IOPHY_PLL_P0_CTL2_TXCLKREF_EN (1 << 5)
 #define XUSB_PADCTL_IOPHY_PLL_P0_CTL2_TXCLKREF_SEL (1 << 4)
 
+#define XUSB_PADCTL_IOPHY_USB3_PADX_CTL2(x) (0x058 + (x) * 4)
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_SHIFT 24
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_MASK 0xff
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_SHIFT 16
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_MASK 0x3f
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_SHIFT 8
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_MASK 0x3f
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_SHIFT 8
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_MASK 0xffff
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_SHIFT 4
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_MASK 0x7
+
+#define XUSB_PADCTL_IOPHY_USB3_PADX_CTL4(x) (0x068 + (x) * 4)
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_SHIFT 24
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_MASK 0x1f
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_SHIFT 16
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_MASK 0x7f
+
+#define XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL2(x) ((x) < 2 ? 0x078 + (x) * 4 : \
+					       0x0f8 + (x) * 4)
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_SHIFT 28
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_MASK 0x3
+
+#define XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL5(x) ((x) < 2 ? 0x090 + (x) * 4 : \
+					       0x11c + (x) * 4)
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL5_RX_QEYE_EN (1 << 8)
+
+#define XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL6(x) ((x) < 2 ? 0x098 + (x) * 4 : \
+					       0x128 + (x) * 4)
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SHIFT 24
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_G_Z_MASK 0x3f
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_TAP_MASK 0x1f
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_AMP_MASK 0x7f
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT 16
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_MASK 0xff
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_G_Z 0x21
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_TAP 0x32
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_AMP 0x33
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_CTLE_Z 0x48
+#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_LATCH_G_Z 0xa1
+
+#define XUSB_PADCTL_USB2_OTG_PADX_CTL0(x) (0x0a0 + (x) * 4)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD_ZI (1 << 21)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD2 (1 << 20)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD (1 << 19)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_SHIFT 14
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_MASK 0x3
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_SHIFT 6
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_MASK 0x3f
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_SHIFT 0
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_MASK 0x3f
+
+#define XUSB_PADCTL_USB2_OTG_PADX_CTL1(x) (0x0ac + (x) * 4)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_HS_IREF_CAP_SHIFT 9
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_HS_IREF_CAP_MASK 0x3
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_SHIFT 3
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_MASK 0x7
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DR (1 << 2)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DISC_FORCE_POWERUP (1 << 1)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_CHRP_FORCE_POWERUP (1 << 0)
+
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0 0x0b8
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD (1 << 12)
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_SHIFT 2
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_MASK 0x7
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_SHIFT 0
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_MASK 0x3
+
+#define XUSB_PADCTL_HSIC_PADX_CTL0(x) (0x0c0 + (x) * 4)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_SHIFT 12
+#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_MASK 0x7
+#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_SHIFT 8
+#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_MASK 0x7
+#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_SHIFT 4
+#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_MASK 0x7
+#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_SHIFT 0
+#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_MASK 0x7
+
+#define XUSB_PADCTL_HSIC_PADX_CTL1(x) (0x0c8 + (x) * 4)
+#define XUSB_PADCTL_HSIC_PAD_CTL1_RPU_STROBE (1 << 10)
+#define XUSB_PADCTL_HSIC_PAD_CTL1_RPU_DATA (1 << 9)
+#define XUSB_PADCTL_HSIC_PAD_CTL1_RPD_STROBE (1 << 8)
+#define XUSB_PADCTL_HSIC_PAD_CTL1_RPD_DATA (1 << 7)
+#define XUSB_PADCTL_HSIC_PAD_CTL1_PD_ZI (1 << 5)
+#define XUSB_PADCTL_HSIC_PAD_CTL1_PD_RX (1 << 4)
+#define XUSB_PADCTL_HSIC_PAD_CTL1_PD_TRX (1 << 3)
+#define XUSB_PADCTL_HSIC_PAD_CTL1_PD_TX (1 << 2)
+#define XUSB_PADCTL_HSIC_PAD_CTL1_AUTO_TERM_EN (1 << 0)
+
+#define XUSB_PADCTL_HSIC_PADX_CTL2(x) (0x0d0 + (x) * 4)
+#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT 4
+#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_MASK 0x7
+#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT 0
+#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_MASK 0x7
+
+#define XUSB_PADCTL_HSIC_STRB_TRIM_CONTROL 0x0e0
+#define XUSB_PADCTL_HSIC_STRB_TRIM_CONTROL_STRB_TRIM_MASK 0x1f
+
 #define XUSB_PADCTL_IOPHY_PLL_S0_CTL1 0x138
 #define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL1_LOCKDET (1 << 27)
 #define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL1_MODE (1 << 24)
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL0_REFCLK_NDIV_SHIFT 20
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL0_REFCLK_NDIV_MASK 0x3
 #define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_PWR_OVRD (1 << 3)
 #define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_RST (1 << 1)
 #define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_IDDQ (1 << 0)
 
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2 0x13c
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL1_CP_CNTL_SHIFT 20
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL1_CP_CNTL_MASK 0xf
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL0_CP_CNTL_SHIFT 16
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL0_CP_CNTL_MASK 0xf
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_TCLKOUT_EN (1 << 12)
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_TXCLKREF_SEL (1 << 4)
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_XDIGCLK_SEL_SHIFT 0
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_XDIGCLK_SEL_MASK 0x7
+
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL3 0x140
+#define XUSB_PADCTL_IOPHY_PLL_S0_CTL3_RCAL_BYPASS (1 << 7)
+
 #define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1 0x148
 #define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1_IDDQ_OVRD (1 << 1)
 #define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1_IDDQ (1 << 0)
 
+#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL2 0x14c
+
+#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL5 0x158
+
+#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL6 0x15c
+
 struct tegra_xusb_padctl_function {
 	const char *name;
 	const char * const *groups;
@@ -72,6 +222,16 @@ struct tegra_xusb_padctl_soc {
 
 	const struct tegra_xusb_padctl_lane *lanes;
 	unsigned int num_lanes;
+
+	u32 rx_wander;
+	u32 rx_eq;
+	u32 cdr_cntl;
+	u32 dfe_cntl;
+	u32 hs_slew;
+	u32 ls_rslew[TEGRA_XUSB_UTMI_PHYS];
+	u32 hs_discon_level;
+	u32 spare_in;
+	unsigned int hsic_port_offset;
 };
 
 struct tegra_xusb_padctl_lane {
@@ -86,6 +246,22 @@ struct tegra_xusb_padctl_lane {
 	unsigned int num_funcs;
 };
 
+struct tegra_xusb_fuse_calibration {
+	u32 hs_curr_level[TEGRA_XUSB_UTMI_PHYS];
+	u32 hs_iref_cap;
+	u32 hs_term_range_adj;
+	u32 hs_squelch_level;
+};
+
+struct tegra_xusb_usb3_port {
+	unsigned int lane;
+	bool context_saved;
+	u32 tap1_val;
+	u32 amp_val;
+	u32 ctle_z_val;
+	u32 ctle_g_val;
+};
+
 struct tegra_xusb_padctl {
 	struct device *dev;
 	void __iomem *regs;
@@ -93,13 +269,25 @@ struct tegra_xusb_padctl {
 	struct reset_control *rst;
 
 	const struct tegra_xusb_padctl_soc *soc;
+	struct tegra_xusb_fuse_calibration calib;
 	struct pinctrl_dev *pinctrl;
 	struct pinctrl_desc desc;
 
 	struct phy_provider *provider;
-	struct phy *phys[2];
+	struct phy *phys[TEGRA_XUSB_NUM_PHYS];
 
 	unsigned int enable;
+
+	struct work_struct mbox_req_work;
+	struct tegra_xusb_mbox_msg mbox_req;
+	struct mbox_client mbox_client;
+	struct mbox_chan *mbox_chan;
+
+	struct tegra_xusb_usb3_port usb3_ports[TEGRA_XUSB_USB3_PHYS];
+	unsigned int utmi_enable;
+	unsigned int hs_curr_level_offset[TEGRA_XUSB_UTMI_PHYS];
+	struct regulator *vbus[TEGRA_XUSB_UTMI_PHYS];
+	struct regulator *vddio_hsic;
 };
 
 static inline void padctl_writel(struct tegra_xusb_padctl *padctl, u32 value,
@@ -114,6 +302,53 @@ static inline u32 padctl_readl(struct tegra_xusb_padctl *padctl,
 	return readl(padctl->regs + offset);
 }
 
+static inline struct tegra_xusb_padctl *
+mbox_work_to_padctl(struct work_struct *work)
+{
+	return container_of(work, struct tegra_xusb_padctl, mbox_req_work);
+}
+
+#define PIN_OTG_0   0
+#define PIN_OTG_1   1
+#define PIN_OTG_2   2
+#define PIN_ULPI_0  3
+#define PIN_HSIC_0  4
+#define PIN_HSIC_1  5
+#define PIN_PCIE_0  6
+#define PIN_PCIE_1  7
+#define PIN_PCIE_2  8
+#define PIN_PCIE_3  9
+#define PIN_PCIE_4 10
+#define PIN_SATA_0 11
+
+static inline bool lane_is_otg(unsigned int lane)
+{
+	return lane >= PIN_OTG_0 && lane <= PIN_OTG_2;
+}
+
+static inline bool lane_is_hsic(unsigned int lane)
+{
+	return lane >= PIN_HSIC_0 && lane <= PIN_HSIC_1;
+}
+
+static inline bool lane_is_pcie_or_sata(unsigned int lane)
+{
+	return lane >= PIN_PCIE_0 && lane <= PIN_SATA_0;
+}
+
+static int lane_to_usb3_port(struct tegra_xusb_padctl *padctl,
+			     unsigned int lane)
+{
+	unsigned int i;
+
+	for (i = 0; i < TEGRA_XUSB_USB3_PHYS; i++) {
+		if (padctl->usb3_ports[i].lane == lane)
+			return i;
+	}
+
+	return -EINVAL;
+}
+
 static int tegra_xusb_padctl_get_groups_count(struct pinctrl_dev *pinctrl)
 {
 	struct tegra_xusb_padctl *padctl = pinctrl_dev_get_drvdata(pinctrl);
@@ -131,6 +366,17 @@ static const char *tegra_xusb_padctl_get_group_name(struct pinctrl_dev *pinctrl,
 
 enum tegra_xusb_padctl_param {
 	TEGRA_XUSB_PADCTL_IDDQ,
+	TEGRA_XUSB_PADCTL_USB3_PORT,
+	TEGRA_XUSB_PADCTL_USB2_PORT,
+	TEGRA_XUSB_PADCTL_HSIC_STROBE_TRIM,
+	TEGRA_XUSB_PADCTL_HSIC_RX_STROBE_TRIM,
+	TEGRA_XUSB_PADCTL_HSIC_RX_DATA_TRIM,
+	TEGRA_XUSB_PADCTL_HSIC_TX_RTUNEN,
+	TEGRA_XUSB_PADCTL_HSIC_TX_RTUNEP,
+	TEGRA_XUSB_PADCTL_HSIC_TX_RSLEWN,
+	TEGRA_XUSB_PADCTL_HSIC_TX_RSLEWP,
+	TEGRA_XUSB_PADCTL_HSIC_AUTO_TERM,
+	TEGRA_XUSB_PADCTL_OTG_HS_CURR_LEVEL_OFFSET,
 };
 
 static const struct tegra_xusb_padctl_property {
@@ -138,6 +384,18 @@ static const struct tegra_xusb_padctl_property {
 	enum tegra_xusb_padctl_param param;
 } properties[] = {
 	{ "nvidia,iddq", TEGRA_XUSB_PADCTL_IDDQ },
+	{ "nvidia,usb3-port", TEGRA_XUSB_PADCTL_USB3_PORT },
+	{ "nvidia,usb2-port", TEGRA_XUSB_PADCTL_USB2_PORT },
+	{ "nvidia,hsic-strobe-trim", TEGRA_XUSB_PADCTL_HSIC_STROBE_TRIM },
+	{ "nvidia,hsic-rx-strobe-trim", TEGRA_XUSB_PADCTL_HSIC_RX_STROBE_TRIM },
+	{ "nvidia,hsic-rx-data-trim", TEGRA_XUSB_PADCTL_HSIC_RX_DATA_TRIM },
+	{ "nvidia,hsic-tx-rtune-n", TEGRA_XUSB_PADCTL_HSIC_TX_RTUNEN },
+	{ "nvidia,hsic-tx-rtune-p", TEGRA_XUSB_PADCTL_HSIC_TX_RTUNEP },
+	{ "nvidia,hsic-tx-rslew-n", TEGRA_XUSB_PADCTL_HSIC_TX_RSLEWN },
+	{ "nvidia,hsic-tx-rslew-p", TEGRA_XUSB_PADCTL_HSIC_TX_RSLEWP },
+	{ "nvidia,hsic-auto-term", TEGRA_XUSB_PADCTL_HSIC_AUTO_TERM },
+	{ "nvidia,otg-hs-curr-level-offset",
+	  TEGRA_XUSB_PADCTL_OTG_HS_CURR_LEVEL_OFFSET },
 };
 
 #define TEGRA_XUSB_PADCTL_PACK(param, value) ((param) << 16 | (value))
@@ -322,6 +580,7 @@ static int tegra_xusb_padctl_pinconf_group_get(struct pinctrl_dev *pinctrl,
 	const struct tegra_xusb_padctl_lane *lane;
 	enum tegra_xusb_padctl_param param;
 	u32 value;
+	int port;
 
 	param = TEGRA_XUSB_PADCTL_UNPACK_PARAM(*config);
 	lane = &padctl->soc->lanes[group];
@@ -338,8 +597,136 @@ static int tegra_xusb_padctl_pinconf_group_get(struct pinctrl_dev *pinctrl,
 			value = 0;
 		else
 			value = 1;
+		break;
 
-		*config = TEGRA_XUSB_PADCTL_PACK(param, value);
+	case TEGRA_XUSB_PADCTL_USB3_PORT:
+		value = lane_to_usb3_port(padctl, group);
+		if (value < 0) {
+			dev_err(padctl->dev,
+				"Pin %d not mapped to USB3 port\n", group);
+			return -EINVAL;
+		}
+		break;
+
+	case TEGRA_XUSB_PADCTL_USB2_PORT:
+		port = lane_to_usb3_port(padctl, group);
+		if (port < 0) {
+			dev_err(padctl->dev,
+				"Pin %d not mapped to USB3 port\n", group);
+			return -EINVAL;
+		}
+
+		value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_MAP) >>
+			XUSB_PADCTL_SS_PORT_MAP_PORTX_SHIFT(port);
+		value &= XUSB_PADCTL_SS_PORT_MAP_PORT_MASK;
+		break;
+
+	case TEGRA_XUSB_PADCTL_HSIC_STROBE_TRIM:
+		if (!lane_is_hsic(group)) {
+			dev_err(padctl->dev, "Pin %d not an HSIC\n", group);
+			return -EINVAL;
+		}
+
+		value = padctl_readl(padctl,
+				     XUSB_PADCTL_HSIC_STRB_TRIM_CONTROL);
+		value &= XUSB_PADCTL_HSIC_STRB_TRIM_CONTROL_STRB_TRIM_MASK;
+		break;
+
+	case TEGRA_XUSB_PADCTL_HSIC_RX_STROBE_TRIM:
+		if (!lane_is_hsic(group)) {
+			dev_err(padctl->dev, "Pin %d not an HSIC\n", group);
+			return -EINVAL;
+		}
+
+		port = group - PIN_HSIC_0;
+		value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL2(port)) >>
+			XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT;
+		value &= XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_MASK;
+		break;
+
+	case TEGRA_XUSB_PADCTL_HSIC_RX_DATA_TRIM:
+		if (!lane_is_hsic(group)) {
+			dev_err(padctl->dev, "Pin %d not an HSIC\n", group);
+			return -EINVAL;
+		}
+
+		port = group - PIN_HSIC_0;
+		value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL2(port)) >>
+			XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT;
+		value &= XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_MASK;
+		break;
+
+	case TEGRA_XUSB_PADCTL_HSIC_TX_RTUNEN:
+		if (!lane_is_hsic(group)) {
+			dev_err(padctl->dev, "Pin %d not an HSIC\n", group);
+			return -EINVAL;
+		}
+
+		port = group - PIN_HSIC_0;
+		value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL0(port)) >>
+			XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_SHIFT;
+		value &= XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_MASK;
+		break;
+
+	case TEGRA_XUSB_PADCTL_HSIC_TX_RTUNEP:
+		if (!lane_is_hsic(group)) {
+			dev_err(padctl->dev, "Pin %d not an HSIC\n", group);
+			return -EINVAL;
+		}
+
+		port = group - PIN_HSIC_0;
+		value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL0(port)) >>
+			XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_SHIFT;
+		value &= XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_MASK;
+		break;
+
+	case TEGRA_XUSB_PADCTL_HSIC_TX_RSLEWN:
+		if (!lane_is_hsic(group)) {
+			dev_err(padctl->dev, "Pin %d not an HSIC\n", group);
+			return -EINVAL;
+		}
+
+		port = group - PIN_HSIC_0;
+		value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL0(port)) >>
+			XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_SHIFT;
+		value &= XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_MASK;
+		break;
+
+	case TEGRA_XUSB_PADCTL_HSIC_TX_RSLEWP:
+		if (!lane_is_hsic(group)) {
+			dev_err(padctl->dev, "Pin %d not an HSIC\n", group);
+			return -EINVAL;
+		}
+
+		port = group - PIN_HSIC_0;
+		value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL0(port)) >>
+			XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_SHIFT;
+		value &= XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_MASK;
+		break;
+
+	case TEGRA_XUSB_PADCTL_HSIC_AUTO_TERM:
+		if (!lane_is_hsic(group)) {
+			dev_err(padctl->dev, "Pin %d not an HSIC\n", group);
+			return -EINVAL;
+		}
+
+		port = group - PIN_HSIC_0;
+		value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL1(port));
+		if (value & XUSB_PADCTL_HSIC_PAD_CTL1_AUTO_TERM_EN)
+			value = 1;
+		else
+			value = 0;
+		break;
+
+	case TEGRA_XUSB_PADCTL_OTG_HS_CURR_LEVEL_OFFSET:
+		if (!lane_is_otg(group)) {
+			dev_err(padctl->dev, "Pin %d is not an OTG pad\n",
+				group);
+			return -EINVAL;
+		}
+
+		port = group - PIN_OTG_0;
+		value = padctl->hs_curr_level_offset[port];
 		break;
 
 	default:
@@ -348,6 +735,7 @@ static int tegra_xusb_padctl_pinconf_group_get(struct pinctrl_dev *pinctrl,
 		return -ENOTSUPP;
 	}
 
+	*config = TEGRA_XUSB_PADCTL_PACK(param, value);
 	return 0;
 }
 
@@ -362,6 +750,7 @@ static int tegra_xusb_padctl_pinconf_group_set(struct pinctrl_dev *pinctrl,
 	unsigned long value;
 	unsigned int i;
 	u32 regval;
+	int port;
 
 	lane = &padctl->soc->lanes[group];
 
@@ -385,6 +774,206 @@ static int tegra_xusb_padctl_pinconf_group_set(struct pinctrl_dev *pinctrl,
 			padctl_writel(padctl, regval, lane->offset);
 			break;
 
+		case TEGRA_XUSB_PADCTL_USB3_PORT:
+			if (value >= TEGRA_XUSB_USB3_PHYS) {
+				dev_err(padctl->dev, "Invalid USB3 port: %lu\n",
+					value);
+				return -EINVAL;
+			}
+			if (!lane_is_pcie_or_sata(group)) {
+				dev_err(padctl->dev,
+					"USB3 port not applicable for pin %d\n",
+					group);
+				return -EINVAL;
+			}
+
+			padctl->usb3_ports[value].lane = group;
+			break;
+
+		case TEGRA_XUSB_PADCTL_USB2_PORT:
+			if (value >= TEGRA_XUSB_UTMI_PHYS) {
+				dev_err(padctl->dev, "Invalid USB2 port: %lu\n",
+					value);
+				return -EINVAL;
+			}
+			if (!lane_is_pcie_or_sata(group)) {
+				dev_err(padctl->dev,
+					"USB2 port not applicable for pin %d\n",
+					group);
+				return -EINVAL;
+			}
+			port = lane_to_usb3_port(padctl, group);
+			if (port < 0) {
+				dev_err(padctl->dev,
+					"Pin %d not mapped to USB3 port\n",
+					group);
+				return -EINVAL;
+			}
+
+			regval = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_MAP);
+			regval &= ~(XUSB_PADCTL_SS_PORT_MAP_PORT_MASK <<
+				    XUSB_PADCTL_SS_PORT_MAP_PORTX_SHIFT(port));
+			regval |= value <<
+				XUSB_PADCTL_SS_PORT_MAP_PORTX_SHIFT(port);
+			padctl_writel(padctl, regval, XUSB_PADCTL_SS_PORT_MAP);
+			break;
+
+		case TEGRA_XUSB_PADCTL_HSIC_STROBE_TRIM:
+			if (!lane_is_hsic(group)) {
+				dev_err(padctl->dev, "Pin %d not an HSIC\n",
+					group);
+				return -EINVAL;
+			}
+
+			value &= XUSB_PADCTL_HSIC_STRB_TRIM_CONTROL_STRB_TRIM_MASK;
+			padctl_writel(padctl, value,
+				      XUSB_PADCTL_HSIC_STRB_TRIM_CONTROL);
+			break;
+
+		case TEGRA_XUSB_PADCTL_HSIC_RX_STROBE_TRIM:
+			if (!lane_is_hsic(group)) {
+				dev_err(padctl->dev, "Pin %d not an HSIC\n",
+					group);
+				return -EINVAL;
+			}
+
+			port = group - PIN_HSIC_0;
+			value &= XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_MASK;
+			regval = padctl_readl(padctl,
+					      XUSB_PADCTL_HSIC_PADX_CTL2(port));
+			regval &= ~(XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_MASK <<
+				    XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT);
+			regval |= value <<
+				XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT;
+			padctl_writel(padctl, regval,
+				      XUSB_PADCTL_HSIC_PADX_CTL2(port));
+			break;
+
+		case TEGRA_XUSB_PADCTL_HSIC_RX_DATA_TRIM:
+			if (!lane_is_hsic(group)) {
+				dev_err(padctl->dev, "Pin %d not an HSIC\n",
+					group);
+				return -EINVAL;
+			}
+
+			port = group - PIN_HSIC_0;
+			value &= XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_MASK;
+			regval = padctl_readl(padctl,
+					      XUSB_PADCTL_HSIC_PADX_CTL2(port));
+			regval &= ~(XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_MASK <<
+				    XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT);
+			regval |= value <<
+				XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT;
+			padctl_writel(padctl, regval,
+				      XUSB_PADCTL_HSIC_PADX_CTL2(port));
+			break;
+
+		case TEGRA_XUSB_PADCTL_HSIC_TX_RTUNEN:
+			if (!lane_is_hsic(group)) {
+				dev_err(padctl->dev, "Pin %d not an HSIC\n",
+					group);
+				return -EINVAL;
+			}
+
+			port = group - PIN_HSIC_0;
+			value &= XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_MASK;
+			regval = padctl_readl(padctl,
+					      XUSB_PADCTL_HSIC_PADX_CTL0(port));
+			regval &= ~(XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_MASK <<
+				    XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_SHIFT);
+			regval |= value <<
+				XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_SHIFT;
+			padctl_writel(padctl, regval,
+				      XUSB_PADCTL_HSIC_PADX_CTL0(port));
+			break;
+
+		case TEGRA_XUSB_PADCTL_HSIC_TX_RTUNEP:
+			if (!lane_is_hsic(group)) {
+				dev_err(padctl->dev, "Pin %d not an HSIC\n",
+					group);
+				return -EINVAL;
+			}
+
+			port = group - PIN_HSIC_0;
+			value &= XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_MASK;
+			regval = padctl_readl(padctl,
+					      XUSB_PADCTL_HSIC_PADX_CTL0(port));
+			regval &= ~(XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_MASK <<
+				    XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_SHIFT);
+			regval |= value <<
+				XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_SHIFT;
+			padctl_writel(padctl, regval,
+				      XUSB_PADCTL_HSIC_PADX_CTL0(port));
+			break;
+
+		case TEGRA_XUSB_PADCTL_HSIC_TX_RSLEWN:
+			if (!lane_is_hsic(group)) {
+				dev_err(padctl->dev, "Pin %d not an HSIC\n",
+					group);
+				return -EINVAL;
+			}
+
+			port = group - PIN_HSIC_0;
+			value &= XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_MASK;
+			regval = padctl_readl(padctl,
+					      XUSB_PADCTL_HSIC_PADX_CTL0(port));
+			regval &= ~(XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_MASK <<
+				    XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_SHIFT);
+			regval |= value <<
+				XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_SHIFT;
+			padctl_writel(padctl, regval,
+				      XUSB_PADCTL_HSIC_PADX_CTL0(port));
+			break;
+
+		case TEGRA_XUSB_PADCTL_HSIC_TX_RSLEWP:
+			if (!lane_is_hsic(group)) {
+				dev_err(padctl->dev, "Pin %d not an HSIC\n",
+					group);
+				return -EINVAL;
+			}
+
+			port = group - PIN_HSIC_0;
+			value &= XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_MASK;
+			regval = padctl_readl(padctl,
+					      XUSB_PADCTL_HSIC_PADX_CTL0(port));
+			regval &= ~(XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_MASK <<
+				    XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_SHIFT);
+			regval |= value <<
+				XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_SHIFT;
+			padctl_writel(padctl, regval,
+				      XUSB_PADCTL_HSIC_PADX_CTL0(port));
+			break;
+
+		case TEGRA_XUSB_PADCTL_HSIC_AUTO_TERM:
+			if (!lane_is_hsic(group)) {
+				dev_err(padctl->dev, "Pin %d not an HSIC\n",
+					group);
+				return -EINVAL;
+			}
+
+			port = group - PIN_HSIC_0;
+			regval = padctl_readl(padctl,
+					      XUSB_PADCTL_HSIC_PADX_CTL1(port));
+			if (!value)
+				regval &= ~XUSB_PADCTL_HSIC_PAD_CTL1_AUTO_TERM_EN;
+			else
+				regval |= XUSB_PADCTL_HSIC_PAD_CTL1_AUTO_TERM_EN;
+			padctl_writel(padctl, regval,
+				      XUSB_PADCTL_HSIC_PADX_CTL1(port));
+			break;
+
+		case TEGRA_XUSB_PADCTL_OTG_HS_CURR_LEVEL_OFFSET:
+			if (!lane_is_otg(group)) {
+				dev_err(padctl->dev,
+					"Pin %d is not an OTG pad\n", group);
+				return -EINVAL;
+			}
+
+			port = group - PIN_OTG_0;
+			value &= XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_MASK;
+			padctl->hs_curr_level_offset[port] = value;
+			break;
+
 		default:
 			dev_err(padctl->dev,
 				"invalid configuration parameter: %04x\n",
@@ -671,6 +1260,548 @@ static const struct phy_ops sata_phy_ops = {
 	.owner = THIS_MODULE,
 };
 
+static int usb3_phy_to_port(struct phy *phy)
+{
+	struct tegra_xusb_padctl *padctl = phy_get_drvdata(phy);
+	unsigned int i;
+
+	for (i = 0; i < TEGRA_XUSB_USB3_PHYS; i++) {
+		if (phy == padctl->phys[TEGRA_XUSB_PADCTL_USB3_P0 + i])
+			return i;
+	}
+	WARN_ON(1);
+
+	return -EINVAL;
+}
+
+static int usb3_phy_power_on(struct phy *phy)
+{
+	struct tegra_xusb_padctl *padctl = phy_get_drvdata(phy);
+	int port = usb3_phy_to_port(phy);
+	unsigned int lane;
+	u32 value, offset;
+
+	if (port < 0)
+		return port;
+
+	lane = padctl->usb3_ports[port].lane;
+	if (!lane_is_pcie_or_sata(lane)) {
+		dev_err(padctl->dev, "USB3 PHY %d mapped to invalid lane: %d\n",
+			port, lane);
+		return -EINVAL;
+	}
+
+	value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_USB3_PADX_CTL2(port));
+	value &= ~((XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_MASK <<
+		    XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_SHIFT) |
+		   (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_MASK <<
+		    XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_SHIFT) |
+		   (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_MASK <<
+		    XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_SHIFT));
+	value |= (padctl->soc->rx_wander <<
+		  XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_SHIFT) |
+		 (padctl->soc->cdr_cntl <<
+		  XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_SHIFT) |
+		 (padctl->soc->rx_eq <<
+		  XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_SHIFT);
+	if (padctl->usb3_ports[port].context_saved) {
+		value &= ~((XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_MASK <<
+			    XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_SHIFT) |
+			   (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_MASK <<
+			    XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_SHIFT));
+		value |= (padctl->usb3_ports[port].ctle_g_val <<
+			  XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_SHIFT) |
+			 (padctl->usb3_ports[port].ctle_z_val <<
+			  XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_SHIFT);
+	}
+	padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_USB3_PADX_CTL2(port));
+
+	value = padctl->soc->dfe_cntl;
+	if (padctl->usb3_ports[port].context_saved) {
+		value &= ~((XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_MASK <<
+			    XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_SHIFT) |
+			   (XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_MASK <<
+			    XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_SHIFT));
+		value |= (padctl->usb3_ports[port].tap1_val <<
+			  XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_SHIFT) |
+			 (padctl->usb3_ports[port].amp_val <<
+			  XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_SHIFT);
+	}
+	padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_USB3_PADX_CTL4(port));
+
+	offset = (lane == PIN_SATA_0) ?
+		XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL2 :
+		XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL2(lane - PIN_PCIE_0);
+	value = padctl_readl(padctl, offset);
+	value &= ~(XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_MASK <<
+		   XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_SHIFT);
+	value |= padctl->soc->spare_in <<
+		XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_SHIFT;
+	padctl_writel(padctl, value, offset);
+
+	offset = (lane == PIN_SATA_0) ?
+		XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL5 :
+		XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL5(lane - PIN_PCIE_0);
+	value = padctl_readl(padctl, offset);
+	value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL5_RX_QEYE_EN;
+	padctl_writel(padctl, value, offset);
+
+	/* Enable SATA PHY when SATA lane is used */
+	if (lane == PIN_SATA_0) {
+		value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+		value &= ~(XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL0_REFCLK_NDIV_MASK <<
+			   XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL0_REFCLK_NDIV_SHIFT);
+		value |= 0x2 <<
+			XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL0_REFCLK_NDIV_SHIFT;
+		padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+
+		value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL2);
+		value &= ~((XUSB_PADCTL_IOPHY_PLL_S0_CTL2_XDIGCLK_SEL_MASK <<
+			    XUSB_PADCTL_IOPHY_PLL_S0_CTL2_XDIGCLK_SEL_SHIFT) |
+			   (XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL1_CP_CNTL_MASK <<
+			    XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL1_CP_CNTL_SHIFT) |
+			   (XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL0_CP_CNTL_MASK <<
+			    XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL0_CP_CNTL_SHIFT) |
+			   XUSB_PADCTL_IOPHY_PLL_S0_CTL2_TCLKOUT_EN);
+		value |= (0x7 <<
+			  XUSB_PADCTL_IOPHY_PLL_S0_CTL2_XDIGCLK_SEL_SHIFT) |
+			 (0x8 <<
+			  XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL1_CP_CNTL_SHIFT) |
+			 (0x8 <<
+			  XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL0_CP_CNTL_SHIFT) |
+			 XUSB_PADCTL_IOPHY_PLL_S0_CTL2_TXCLKREF_SEL;
+		padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL2);
+
+		value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL3);
+		value &= ~XUSB_PADCTL_IOPHY_PLL_S0_CTL3_RCAL_BYPASS;
+		padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL3);
+	}
+
+	value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+	value &= ~XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_CLAMP_EN_EARLY(port);
+	padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+	usleep_range(100, 200);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+	value &= ~XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_CLAMP_EN_EARLY(port);
+	padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+	value &= ~XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_VCORE_DOWN(port);
+	padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+	return 0;
+}
+
+static int usb3_phy_power_off(struct phy *phy)
+{
+	struct tegra_xusb_padctl *padctl = phy_get_drvdata(phy);
+	int port = usb3_phy_to_port(phy);
+	u32 value;
+
+	if (port < 0)
+		return port;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+	value |= XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_CLAMP_EN_EARLY(port);
+	padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+	usleep_range(100, 200);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+	value |= XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_CLAMP_EN_EARLY(port);
+	padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+	usleep_range(250, 350);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+	value |= XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_VCORE_DOWN(port);
+	padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+	return 0;
+}
+
+static int usb3_phy_save_context(struct tegra_xusb_padctl *padctl,
+				 unsigned int port)
+{
+	unsigned int lane = padctl->usb3_ports[port].lane;
+	u32 value, offset;
+
+	if (port >= TEGRA_XUSB_USB3_PHYS)
+		return -EINVAL;
+
+	padctl->usb3_ports[port].context_saved = true;
+
+	offset = (lane == PIN_SATA_0) ?
+		XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL6 :
+		XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL6(lane - PIN_PCIE_0);
+
+	value = padctl_readl(padctl, offset);
+	value &= ~(XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_MASK <<
+		   XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT);
+	value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_TAP <<
+		XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT;
+	padctl_writel(padctl, value, offset);
+
+	value = padctl_readl(padctl, offset) >>
+		XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SHIFT;
+	padctl->usb3_ports[port].tap1_val = value &
+		XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_TAP_MASK;
+
+	value = padctl_readl(padctl, offset);
+	value &= ~(XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_MASK <<
+		   XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT);
+	value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_AMP <<
+		XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT;
+	padctl_writel(padctl, value, offset);
+
+	value = padctl_readl(padctl, offset) >>
+		XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SHIFT;
+	padctl->usb3_ports[port].amp_val = value &
+		XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_AMP_MASK;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_USB3_PADX_CTL4(port));
+	value &= ~((XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_MASK <<
+		    XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_SHIFT) |
+		   (XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_MASK <<
+		    XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_SHIFT));
+	value |= (padctl->usb3_ports[port].tap1_val <<
+		  XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_SHIFT) |
+		 (padctl->usb3_ports[port].amp_val <<
+		  XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_SHIFT);
+	padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_USB3_PADX_CTL4(port));
+
+	value = padctl_readl(padctl, offset);
+	value &= ~(XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_MASK <<
+		   XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT);
+	value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_LATCH_G_Z <<
+		XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT;
+	padctl_writel(padctl, value, offset);
+
+	value = padctl_readl(padctl, offset);
+	value &= ~(XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_MASK <<
+		   XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT);
+	value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_G_Z <<
+		XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT;
+	padctl_writel(padctl, value, offset);
+
+	value = padctl_readl(padctl, offset) >>
+		XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SHIFT;
+	padctl->usb3_ports[port].ctle_g_val = value &
+		XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_G_Z_MASK;
+
+	value = padctl_readl(padctl, offset);
+	value &= ~(XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_MASK <<
+		   XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT);
+	value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_CTLE_Z <<
+		XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT;
+	padctl_writel(padctl, value, offset);
+
+	value = padctl_readl(padctl, offset) >>
+		XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SHIFT;
+	padctl->usb3_ports[port].ctle_z_val = value &
+		XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_G_Z_MASK;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_USB3_PADX_CTL2(port));
+	value &= ~((XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_MASK <<
+		    XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_SHIFT) |
+		   (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_MASK <<
+		    XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_SHIFT));
+	value |= (padctl->usb3_ports[port].ctle_g_val <<
+		  XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_SHIFT) |
+		 (padctl->usb3_ports[port].ctle_z_val <<
+		  XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_SHIFT);
+	padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_USB3_PADX_CTL2(port));
+
+	return 0;
+}
+
+static const struct phy_ops usb3_phy_ops = {
+	.init = tegra_xusb_phy_init,
+	.exit = tegra_xusb_phy_exit,
+	.power_on = usb3_phy_power_on,
+	.power_off = usb3_phy_power_off,
+	.owner = THIS_MODULE,
+};
+
+static int utmi_phy_to_port(struct phy *phy)
+{
+	struct tegra_xusb_padctl *padctl = phy_get_drvdata(phy);
+	unsigned int i;
+
+	for (i = 0; i < TEGRA_XUSB_UTMI_PHYS; i++) {
+		if (phy == padctl->phys[TEGRA_XUSB_PADCTL_UTMI_P0 + i])
+			return i;
+	}
+	WARN_ON(1);
+
+	return -EINVAL;
+}
+
+static int utmi_phy_power_on(struct phy *phy)
+{
+	struct tegra_xusb_padctl *padctl = phy_get_drvdata(phy);
+	int port = utmi_phy_to_port(phy);
+	int err;
+	u32 value;
+
+	if (port < 0)
+		return port;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0);
+	value &= ~((XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_MASK <<
+		    XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_SHIFT) |
+		   (XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_MASK <<
+		    XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_SHIFT));
+	value |= (padctl->calib.hs_squelch_level <<
+		  XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_SHIFT) |
+		 (padctl->soc->hs_discon_level <<
+		  XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_SHIFT);
+	padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_USB2_PORT_CAP);
+	value &= ~(XUSB_PADCTL_USB2_PORT_CAP_PORT_CAP_MASK <<
+		   XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_SHIFT(port));
+	value |= XUSB_PADCTL_USB2_PORT_CAP_HOST <<
+		XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_SHIFT(port);
+	padctl_writel(padctl, value, XUSB_PADCTL_USB2_PORT_CAP);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL0(port));
+	value &= ~((XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_MASK <<
+		    XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_SHIFT) |
+		   (XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_MASK <<
+		    XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_SHIFT) |
+		   (XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_MASK <<
+		    XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_SHIFT) |
+		   XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD |
+		   XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD2 |
+		   XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD_ZI);
+	value |= (padctl->calib.hs_curr_level[port] +
+		  padctl->hs_curr_level_offset[port]) <<
+		XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_SHIFT;
+	value |= padctl->soc->hs_slew <<
+		XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_SHIFT;
+	value |= padctl->soc->ls_rslew[port] <<
+		XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_SHIFT;
+	padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL0(port));
+
+	value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL1(port));
+	value &= ~((XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_MASK <<
+		    XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_SHIFT) |
+		   (XUSB_PADCTL_USB2_OTG_PAD_CTL1_HS_IREF_CAP_MASK <<
+		    XUSB_PADCTL_USB2_OTG_PAD_CTL1_HS_IREF_CAP_SHIFT) |
+		   XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DR |
+		   XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_CHRP_FORCE_POWERUP |
+		   XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DISC_FORCE_POWERUP);
+	value |= (padctl->calib.hs_term_range_adj <<
+		  XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_SHIFT) |
+		 (padctl->calib.hs_iref_cap <<
+		  XUSB_PADCTL_USB2_OTG_PAD_CTL1_HS_IREF_CAP_SHIFT);
+	padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL1(port));
+
+	err = regulator_enable(padctl->vbus[port]);
+	if (err)
+		return err;
+
+	mutex_lock(&padctl->lock);
+
+	if (padctl->utmi_enable++ > 0)
+		goto out;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0);
+	value &= ~XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD;
+	padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0);
+
+out:
+	mutex_unlock(&padctl->lock);
+	return 0;
+}
+
+static int utmi_phy_power_off(struct phy *phy)
+{
+	struct tegra_xusb_padctl *padctl = phy_get_drvdata(phy);
+	int port = utmi_phy_to_port(phy);
+	u32 value;
+
+	if (port < 0)
+		return port;
+
+	regulator_disable(padctl->vbus[port]);
+
+	mutex_lock(&padctl->lock);
+
+	if (WARN_ON(padctl->utmi_enable == 0))
+		goto out;
+
+	if (--padctl->utmi_enable > 0)
+		goto out;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0);
+	value |= XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD;
+	padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0);
+
+out:
+	mutex_unlock(&padctl->lock);
+	return 0;
+}
+
+static const struct phy_ops utmi_phy_ops = {
+	.init = tegra_xusb_phy_init,
+	.exit = tegra_xusb_phy_exit,
+	.power_on = utmi_phy_power_on,
+	.power_off = utmi_phy_power_off,
+	.owner = THIS_MODULE,
+};
+
+static int hsic_phy_to_port(struct phy *phy)
+{
+	struct tegra_xusb_padctl *padctl = phy_get_drvdata(phy);
+	unsigned int i;
+
+	for (i = 0; i < TEGRA_XUSB_HSIC_PHYS; i++) {
+		if (phy == padctl->phys[TEGRA_XUSB_PADCTL_HSIC_P0 + i])
+			return i;
+	}
+	WARN_ON(1);
+
+	return -EINVAL;
+}
+
+static int hsic_phy_power_on(struct phy *phy)
+{
+	struct tegra_xusb_padctl *padctl = phy_get_drvdata(phy);
+	int port = hsic_phy_to_port(phy);
+	int err;
+	u32 value;
+
+	if (port < 0)
+		return port;
+
+	err = regulator_enable(padctl->vddio_hsic);
+	if (err)
+		return err;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL1(port));
+	value &= ~(XUSB_PADCTL_HSIC_PAD_CTL1_RPD_STROBE |
+		   XUSB_PADCTL_HSIC_PAD_CTL1_RPU_DATA |
+		   XUSB_PADCTL_HSIC_PAD_CTL1_PD_RX |
+		   XUSB_PADCTL_HSIC_PAD_CTL1_PD_ZI |
+		   XUSB_PADCTL_HSIC_PAD_CTL1_PD_TRX |
+		   XUSB_PADCTL_HSIC_PAD_CTL1_PD_TX);
+	value |= XUSB_PADCTL_HSIC_PAD_CTL1_RPD_DATA |
+		 XUSB_PADCTL_HSIC_PAD_CTL1_RPU_STROBE;
+	padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL1(port));
+
+	return 0;
+}
+
+static int hsic_phy_power_off(struct phy *phy)
+{
+	struct tegra_xusb_padctl *padctl = phy_get_drvdata(phy);
+	int port = hsic_phy_to_port(phy);
+	u32 value;
+
+	if (port < 0)
+		return port;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL1(port));
+	value |= XUSB_PADCTL_HSIC_PAD_CTL1_PD_RX |
+		 XUSB_PADCTL_HSIC_PAD_CTL1_PD_ZI |
+		 XUSB_PADCTL_HSIC_PAD_CTL1_PD_TRX |
+		 XUSB_PADCTL_HSIC_PAD_CTL1_PD_TX;
+	padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL1(port));
+
+	regulator_disable(padctl->vddio_hsic);
+
+	return 0;
+}
+
+static void hsic_phy_set_idle(struct tegra_xusb_padctl *padctl,
+			      unsigned int port, bool idle)
+{
+	u32 value;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL1(port));
+	if (idle)
+		value |= XUSB_PADCTL_HSIC_PAD_CTL1_RPD_DATA |
+			 XUSB_PADCTL_HSIC_PAD_CTL1_RPU_STROBE;
+	else
+		value &= ~(XUSB_PADCTL_HSIC_PAD_CTL1_RPD_DATA |
+			   XUSB_PADCTL_HSIC_PAD_CTL1_RPU_STROBE);
+	padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL1(port));
+}
+
+static const struct phy_ops hsic_phy_ops = {
+	.init = tegra_xusb_phy_init,
+	.exit = tegra_xusb_phy_exit,
+	.power_on = hsic_phy_power_on,
+	.power_off = hsic_phy_power_off,
+	.owner = THIS_MODULE,
+};
+
+static void tegra_xusb_phy_mbox_work(struct work_struct *work)
+{
+	struct tegra_xusb_padctl *padctl = mbox_work_to_padctl(work);
+	struct tegra_xusb_mbox_msg *msg = &padctl->mbox_req;
+	struct tegra_xusb_mbox_msg resp;
+	unsigned int i;
+	u32 ports;
+
+	resp.cmd = 0;
+	switch (msg->cmd) {
+	case MBOX_CMD_SAVE_DFE_CTLE_CTX:
+		resp.data = msg->data;
+		if (usb3_phy_save_context(padctl, msg->data) < 0)
+			resp.cmd = MBOX_CMD_NAK;
+		else
+			resp.cmd = MBOX_CMD_ACK;
+		break;
+	case MBOX_CMD_START_HSIC_IDLE:
+	case MBOX_CMD_STOP_HSIC_IDLE:
+		ports = msg->data >> (padctl->soc->hsic_port_offset + 1);
+		resp.data = msg->data;
+		resp.cmd = MBOX_CMD_ACK;
+		for (i = 0; i < TEGRA_XUSB_HSIC_PHYS; i++) {
+			if (!(ports & BIT(i)))
+				continue;
+			if (msg->cmd == MBOX_CMD_START_HSIC_IDLE)
+				hsic_phy_set_idle(padctl, i, true);
+			else
+				hsic_phy_set_idle(padctl, i, false);
+		}
+		break;
+	default:
+		break;
+	}
+
+	if (resp.cmd)
+		mbox_send_message(padctl->mbox_chan, &resp);
+}
+
+static bool is_phy_mbox_message(u32 cmd)
+{
+	switch (cmd) {
+	case MBOX_CMD_SAVE_DFE_CTLE_CTX:
+	case MBOX_CMD_START_HSIC_IDLE:
+	case MBOX_CMD_STOP_HSIC_IDLE:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static void tegra_xusb_phy_mbox_rx(struct mbox_client *cl, void *data)
+{
+	struct tegra_xusb_padctl *padctl = dev_get_drvdata(cl->dev);
+	struct tegra_xusb_mbox_msg *msg = data;
+
+	if (is_phy_mbox_message(msg->cmd)) {
+		padctl->mbox_req = *msg;
+		schedule_work(&padctl->mbox_req_work);
+	}
+}
+
 static struct phy *tegra_xusb_padctl_xlate(struct device *dev,
 					   struct of_phandle_args *args)
 {
@@ -680,25 +1811,12 @@ static struct phy *tegra_xusb_padctl_xlate(struct device *dev,
 	if (args->args_count <= 0)
 		return ERR_PTR(-EINVAL);
 
-	if (index >= ARRAY_SIZE(padctl->phys))
+	if (index >= ARRAY_SIZE(padctl->phys) && !padctl->phys[index])
 		return ERR_PTR(-EINVAL);
 
 	return padctl->phys[index];
 }
 
-#define PIN_OTG_0   0
-#define PIN_OTG_1   1
-#define PIN_OTG_2   2
-#define PIN_ULPI_0  3
-#define PIN_HSIC_0  4
-#define PIN_HSIC_1  5
-#define PIN_PCIE_0  6
-#define PIN_PCIE_1  7
-#define PIN_PCIE_2  8
-#define PIN_PCIE_3  9
-#define PIN_PCIE_4 10
-#define PIN_SATA_0 11
-
 static const struct pinctrl_pin_desc tegra124_pins[] = {
 	PINCTRL_PIN(PIN_OTG_0,  "otg-0"),
 	PINCTRL_PIN(PIN_OTG_1,  "otg-1"),
@@ -856,6 +1974,15 @@ static const struct tegra_xusb_padctl_soc tegra124_soc = {
 	.functions = tegra124_functions,
 	.num_lanes = ARRAY_SIZE(tegra124_lanes),
 	.lanes = tegra124_lanes,
+	.rx_wander = 0xf,
+	.rx_eq = 0xf070,
+	.cdr_cntl = 0x24,
+	.dfe_cntl = 0x002008ee,
+	.hs_slew = 0xe,
+	.ls_rslew = {0x3, 0x0, 0x0},
+	.hs_discon_level = 0x5,
+	.spare_in = 0x1,
+	.hsic_port_offset = 6,
 };
 
 static const struct of_device_id tegra_xusb_padctl_of_match[] = {
@@ -864,6 +1991,80 @@ static const struct of_device_id tegra_xusb_padctl_of_match[] = {
 };
 MODULE_DEVICE_TABLE(of, tegra_xusb_padctl_of_match);
 
+static int tegra_xusb_read_fuse_calibration(struct tegra_xusb_padctl *padctl)
+{
+	unsigned int i;
+	int err;
+	u32 value;
+
+	err = tegra_fuse_readl(TEGRA_FUSE_SKU_CALIB_0, &value);
+	if (err < 0)
+		return err;
+
+	for (i = 0; i < TEGRA_XUSB_UTMI_PHYS; i++) {
+		padctl->calib.hs_curr_level[i] =
+			(value >> FUSE_SKU_CALIB_HS_CURR_LEVEL_PADX_SHIFT(i)) &
+			FUSE_SKU_CALIB_HS_CURR_LEVEL_PAD_MASK;
+	}
+	padctl->calib.hs_iref_cap =
+		(value >> FUSE_SKU_CALIB_HS_IREF_CAP_SHIFT) &
+		FUSE_SKU_CALIB_HS_IREF_CAP_MASK;
+	padctl->calib.hs_term_range_adj =
+		(value >> FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_SHIFT) &
+		FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_MASK;
+	padctl->calib.hs_squelch_level =
+		(value >> FUSE_SKU_CALIB_HS_SQUELCH_LEVEL_SHIFT) &
+		FUSE_SKU_CALIB_HS_SQUELCH_LEVEL_MASK;
+
+	return 0;
+}
+
+static int tegra_xusb_setup_usb(struct tegra_xusb_padctl *padctl)
+{
+	struct phy *phy;
+	unsigned int i;
+
+	for (i = 0; i < TEGRA_XUSB_USB3_PHYS; i++) {
+		phy = devm_phy_create(padctl->dev, NULL, &usb3_phy_ops, NULL);
+		if (IS_ERR(phy))
+			return PTR_ERR(phy);
+
+		padctl->phys[TEGRA_XUSB_PADCTL_USB3_P0 + i] = phy;
+		phy_set_drvdata(phy, padctl);
+	}
+
+	for (i = 0; i < TEGRA_XUSB_UTMI_PHYS; i++) {
+		char reg_name[sizeof("vbus-N")];
+
+		sprintf(reg_name, "vbus-%d", i);
+		padctl->vbus[i] = devm_regulator_get(padctl->dev, reg_name);
+		if (IS_ERR(padctl->vbus[i]))
+			return PTR_ERR(padctl->vbus[i]);
+
+		phy = devm_phy_create(padctl->dev, NULL, &utmi_phy_ops, NULL);
+		if (IS_ERR(phy))
+			return PTR_ERR(phy);
+
+		padctl->phys[TEGRA_XUSB_PADCTL_UTMI_P0 + i] = phy;
+		phy_set_drvdata(phy, padctl);
+	}
+
+	padctl->vddio_hsic = devm_regulator_get(padctl->dev, "vddio-hsic");
+	if (IS_ERR(padctl->vddio_hsic))
+		return PTR_ERR(padctl->vddio_hsic);
+
+	for (i = 0; i < TEGRA_XUSB_HSIC_PHYS; i++) {
+		phy = devm_phy_create(padctl->dev, NULL, &hsic_phy_ops, NULL);
+		if (IS_ERR(phy))
+			return PTR_ERR(phy);
+
+		padctl->phys[TEGRA_XUSB_PADCTL_HSIC_P0 + i] = phy;
+		phy_set_drvdata(phy, padctl);
+	}
+
+	return 0;
+}
+
 static int tegra_xusb_padctl_probe(struct platform_device *pdev)
 {
 	struct tegra_xusb_padctl *padctl;
@@ -888,6 +2089,10 @@ static int tegra_xusb_padctl_probe(struct platform_device *pdev)
 	if (IS_ERR(padctl->regs))
 		return PTR_ERR(padctl->regs);
 
+	err = tegra_xusb_read_fuse_calibration(padctl);
+	if (err < 0)
+		return err;
+
 	padctl->rst = devm_reset_control_get(&pdev->dev, NULL);
 	if (IS_ERR(padctl->rst))
 		return PTR_ERR(padctl->rst);
@@ -928,6 +2133,26 @@ static int tegra_xusb_padctl_probe(struct platform_device *pdev)
 	padctl->phys[TEGRA_XUSB_PADCTL_SATA] = phy;
 	phy_set_drvdata(phy, padctl);
 
+	INIT_WORK(&padctl->mbox_req_work, tegra_xusb_phy_mbox_work);
+	padctl->mbox_client.dev = &pdev->dev;
+	padctl->mbox_client.tx_block = true;
+	padctl->mbox_client.tx_tout = 0;
+	padctl->mbox_client.rx_callback = tegra_xusb_phy_mbox_rx;
+	padctl->mbox_chan = mbox_request_channel(&padctl->mbox_client, 0);
+	if (IS_ERR(padctl->mbox_chan)) {
+		err = PTR_ERR(padctl->mbox_chan);
+		if (err == -EPROBE_DEFER) {
+			goto unregister;
+		} else {
+			dev_warn(&pdev->dev,
+				 "failed to get mailbox, USB support disabled");
+		}
+	} else {
+		err = tegra_xusb_setup_usb(padctl);
+		if (err)
+			goto unregister;
+	}
+
 	padctl->provider = devm_of_phy_provider_register(&pdev->dev,
 							 tegra_xusb_padctl_xlate);
 	if (IS_ERR(padctl->provider)) {
@@ -950,6 +2175,11 @@ static int tegra_xusb_padctl_remove(struct platform_device *pdev)
 	struct tegra_xusb_padctl *padctl = platform_get_drvdata(pdev);
 	int err;
 
+	if (!IS_ERR(padctl->mbox_chan)) {
+		cancel_work_sync(&padctl->mbox_req_work);
+		mbox_free_channel(padctl->mbox_chan);
+	}
+
 	pinctrl_unregister(padctl->pinctrl);
 
 	err = reset_control_assert(padctl->rst);
diff --git a/include/soc/tegra/xusb.h b/include/soc/tegra/xusb.h
index 5ce5e12..0136dc1 100644
--- a/include/soc/tegra/xusb.h
+++ b/include/soc/tegra/xusb.h
@@ -10,6 +10,13 @@
 #ifndef __SOC_TEGRA_XUSB_H__
 #define __SOC_TEGRA_XUSB_H__
 
+#define TEGRA_XUSB_USB3_PHYS 2
+#define TEGRA_XUSB_UTMI_PHYS 3
+#define TEGRA_XUSB_HSIC_PHYS 2
+#define TEGRA_XUSB_NUM_USB_PHYS (TEGRA_XUSB_USB3_PHYS + TEGRA_XUSB_UTMI_PHYS + \
+				 TEGRA_XUSB_HSIC_PHYS)
+#define TEGRA_XUSB_NUM_PHYS (TEGRA_XUSB_NUM_USB_PHYS + 2) /* + SATA & PCIe */
+
 /* Command requests from the firmware */
 enum tegra_xusb_mbox_cmd {
 	MBOX_CMD_MSG_ENABLED = 1,
-- 
2.1.0.rc2.206.gedb03e5




More information about the linux-arm-kernel mailing list