Merge branches 'tegra/pci' and 'tegra/usb' into next/late

This is a prerequisite for enabling the Tegra XUSB, all the
branches should be merged already at the time we get here.

* tegra/pci:
  PCI: tegra: Support per-lane PHYs
  dt-bindings: pci: tegra: Update for per-lane PHYs
  phy: tegra: Add Tegra210 support
  phy: Add Tegra XUSB pad controller support
  dt-bindings: phy: tegra-xusb-padctl: Add Tegra210 support
  dt-bindings: phy: Add NVIDIA Tegra XUSB pad controller binding
  phy: core: Allow children node to be overridden
  clk: tegra: Add interface to enable hardware control of SATA/XUSB PLLs

* tegra/usb:
  usb: xhci: tegra: Add Tegra210 support
  usb: xhci: Add NVIDIA Tegra XUSB controller driver
  dt-bindings: usb: xhci-tegra: Add Tegra210 XUSB controller support
  dt-bindings: usb: Add NVIDIA Tegra XUSB controller binding
  phy: tegra: Add Tegra210 support
  phy: Add Tegra XUSB pad controller support
  dt-bindings: phy: tegra-xusb-padctl: Add Tegra210 support
  dt-bindings: phy: Add NVIDIA Tegra XUSB pad controller binding
  phy: core: Allow children node to be overridden
  clk: tegra: Add interface to enable hardware control of SATA/XUSB PLLs
diff --git a/Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.txt b/Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.txt
index 75321ae..b8cc395 100644
--- a/Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.txt
+++ b/Documentation/devicetree/bindings/pci/nvidia,tegra20-pcie.txt
@@ -60,11 +60,14 @@
   - afi
   - pcie_x
 
-Required properties on Tegra124 and later:
+Required properties on Tegra124 and later (deprecated):
 - phys: Must contain an entry for each entry in phy-names.
 - phy-names: Must include the following entries:
   - pcie
 
+These properties are deprecated in favour of per-lane PHYs define in each of
+the root ports (see below).
+
 Power supplies for Tegra20:
 - avdd-pex-supply: Power supply for analog PCIe logic. Must supply 1.05 V.
 - vdd-pex-supply: Power supply for digital PCIe I/O. Must supply 1.05 V.
@@ -122,11 +125,22 @@
   - Root port 0 uses 4 lanes, root port 1 is unused.
   - Both root ports use 2 lanes.
 
-Example:
+Required properties for Tegra124 and later:
+- phys: Must contain an phandle to a PHY for each entry in phy-names.
+- phy-names: Must include an entry for each active lane. Note that the number
+  of entries does not have to (though usually will) be equal to the specified
+  number of lanes in the nvidia,num-lanes property. Entries are of the form
+  "pcie-N": where N ranges from 0 to the value specified in nvidia,num-lanes.
+
+Examples:
+=========
+
+Tegra20:
+--------
 
 SoC DTSI:
 
-	pcie-controller {
+	pcie-controller@80003000 {
 		compatible = "nvidia,tegra20-pcie";
 		device_type = "pci";
 		reg = <0x80003000 0x00000800   /* PADS registers */
@@ -186,10 +200,9 @@
 		};
 	};
 
-
 Board DTS:
 
-	pcie-controller {
+	pcie-controller@80003000 {
 		status = "okay";
 
 		vdd-supply = <&pci_vdd_reg>;
@@ -222,3 +235,204 @@
 device nodes need to be added in order to allow the bus' children to be
 instantiated at the proper location in the operating system's device tree (as
 illustrated by the optional nodes in the example above).
+
+Tegra30:
+--------
+
+SoC DTSI:
+
+	pcie-controller@00003000 {
+		compatible = "nvidia,tegra30-pcie";
+		device_type = "pci";
+		reg = <0x00003000 0x00000800   /* PADS registers */
+		       0x00003800 0x00000200   /* AFI registers */
+		       0x10000000 0x10000000>; /* configuration space */
+		reg-names = "pads", "afi", "cs";
+		interrupts = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH   /* controller interrupt */
+			      GIC_SPI 99 IRQ_TYPE_LEVEL_HIGH>; /* MSI interrupt */
+		interrupt-names = "intr", "msi";
+
+		#interrupt-cells = <1>;
+		interrupt-map-mask = <0 0 0 0>;
+		interrupt-map = <0 0 0 0 &intc GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>;
+
+		bus-range = <0x00 0xff>;
+		#address-cells = <3>;
+		#size-cells = <2>;
+
+		ranges = <0x82000000 0 0x00000000 0x00000000 0 0x00001000   /* port 0 configuration space */
+			  0x82000000 0 0x00001000 0x00001000 0 0x00001000   /* port 1 configuration space */
+			  0x82000000 0 0x00004000 0x00004000 0 0x00001000   /* port 2 configuration space */
+			  0x81000000 0 0          0x02000000 0 0x00010000   /* downstream I/O */
+			  0x82000000 0 0x20000000 0x20000000 0 0x08000000   /* non-prefetchable memory */
+			  0xc2000000 0 0x28000000 0x28000000 0 0x18000000>; /* prefetchable memory */
+
+		clocks = <&tegra_car TEGRA30_CLK_PCIE>,
+			 <&tegra_car TEGRA30_CLK_AFI>,
+			 <&tegra_car TEGRA30_CLK_PLL_E>,
+			 <&tegra_car TEGRA30_CLK_CML0>;
+		clock-names = "pex", "afi", "pll_e", "cml";
+		resets = <&tegra_car 70>,
+			 <&tegra_car 72>,
+			 <&tegra_car 74>;
+		reset-names = "pex", "afi", "pcie_x";
+		status = "disabled";
+
+		pci@1,0 {
+			device_type = "pci";
+			assigned-addresses = <0x82000800 0 0x00000000 0 0x1000>;
+			reg = <0x000800 0 0 0 0>;
+			status = "disabled";
+
+			#address-cells = <3>;
+			#size-cells = <2>;
+			ranges;
+
+			nvidia,num-lanes = <2>;
+		};
+
+		pci@2,0 {
+			device_type = "pci";
+			assigned-addresses = <0x82001000 0 0x00001000 0 0x1000>;
+			reg = <0x001000 0 0 0 0>;
+			status = "disabled";
+
+			#address-cells = <3>;
+			#size-cells = <2>;
+			ranges;
+
+			nvidia,num-lanes = <2>;
+		};
+
+		pci@3,0 {
+			device_type = "pci";
+			assigned-addresses = <0x82001800 0 0x00004000 0 0x1000>;
+			reg = <0x001800 0 0 0 0>;
+			status = "disabled";
+
+			#address-cells = <3>;
+			#size-cells = <2>;
+			ranges;
+
+			nvidia,num-lanes = <2>;
+		};
+	};
+
+Board DTS:
+
+	pcie-controller@00003000 {
+		status = "okay";
+
+		avdd-pexa-supply = <&ldo1_reg>;
+		vdd-pexa-supply = <&ldo1_reg>;
+		avdd-pexb-supply = <&ldo1_reg>;
+		vdd-pexb-supply = <&ldo1_reg>;
+		avdd-pex-pll-supply = <&ldo1_reg>;
+		avdd-plle-supply = <&ldo1_reg>;
+		vddio-pex-ctl-supply = <&sys_3v3_reg>;
+		hvdd-pex-supply = <&sys_3v3_pexs_reg>;
+
+		pci@1,0 {
+			status = "okay";
+		};
+
+		pci@3,0 {
+			status = "okay";
+		};
+	};
+
+Tegra124:
+---------
+
+SoC DTSI:
+
+	pcie-controller@01003000 {
+		compatible = "nvidia,tegra124-pcie";
+		device_type = "pci";
+		reg = <0x0 0x01003000 0x0 0x00000800   /* PADS registers */
+		       0x0 0x01003800 0x0 0x00000800   /* AFI registers */
+		       0x0 0x02000000 0x0 0x10000000>; /* configuration space */
+		reg-names = "pads", "afi", "cs";
+		interrupts = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>, /* controller interrupt */
+			     <GIC_SPI 99 IRQ_TYPE_LEVEL_HIGH>; /* MSI interrupt */
+		interrupt-names = "intr", "msi";
+
+		#interrupt-cells = <1>;
+		interrupt-map-mask = <0 0 0 0>;
+		interrupt-map = <0 0 0 0 &gic GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>;
+
+		bus-range = <0x00 0xff>;
+		#address-cells = <3>;
+		#size-cells = <2>;
+
+		ranges = <0x82000000 0 0x01000000 0x0 0x01000000 0 0x00001000   /* port 0 configuration space */
+			  0x82000000 0 0x01001000 0x0 0x01001000 0 0x00001000   /* port 1 configuration space */
+			  0x81000000 0 0x0        0x0 0x12000000 0 0x00010000   /* downstream I/O (64 KiB) */
+			  0x82000000 0 0x13000000 0x0 0x13000000 0 0x0d000000   /* non-prefetchable memory (208 MiB) */
+			  0xc2000000 0 0x20000000 0x0 0x20000000 0 0x20000000>; /* prefetchable memory (512 MiB) */
+
+		clocks = <&tegra_car TEGRA124_CLK_PCIE>,
+			 <&tegra_car TEGRA124_CLK_AFI>,
+			 <&tegra_car TEGRA124_CLK_PLL_E>,
+			 <&tegra_car TEGRA124_CLK_CML0>;
+		clock-names = "pex", "afi", "pll_e", "cml";
+		resets = <&tegra_car 70>,
+			 <&tegra_car 72>,
+			 <&tegra_car 74>;
+		reset-names = "pex", "afi", "pcie_x";
+		status = "disabled";
+
+		pci@1,0 {
+			device_type = "pci";
+			assigned-addresses = <0x82000800 0 0x01000000 0 0x1000>;
+			reg = <0x000800 0 0 0 0>;
+			status = "disabled";
+
+			#address-cells = <3>;
+			#size-cells = <2>;
+			ranges;
+
+			nvidia,num-lanes = <2>;
+		};
+
+		pci@2,0 {
+			device_type = "pci";
+			assigned-addresses = <0x82001000 0 0x01001000 0 0x1000>;
+			reg = <0x001000 0 0 0 0>;
+			status = "disabled";
+
+			#address-cells = <3>;
+			#size-cells = <2>;
+			ranges;
+
+			nvidia,num-lanes = <1>;
+		};
+	};
+
+Board DTS:
+
+	pcie-controller@01003000 {
+		status = "okay";
+
+		avddio-pex-supply = <&vdd_1v05_run>;
+		dvddio-pex-supply = <&vdd_1v05_run>;
+		avdd-pex-pll-supply = <&vdd_1v05_run>;
+		hvdd-pex-supply = <&vdd_3v3_lp0>;
+		hvdd-pex-pll-e-supply = <&vdd_3v3_lp0>;
+		vddio-pex-ctl-supply = <&vdd_3v3_lp0>;
+		avdd-pll-erefe-supply = <&avdd_1v05_run>;
+
+		/* Mini PCIe */
+		pci@1,0 {
+			phys = <&{/padctl@7009f000/pads/pcie/lanes/pcie-4}>;
+			phy-names = "pcie-0";
+			status = "okay";
+		};
+
+		/* Gigabit Ethernet */
+		pci@2,0 {
+			phys = <&{/padctl@7009f000/pads/pcie/lanes/pcie-2}>;
+			phy-names = "pcie-0";
+			status = "okay";
+		};
+	};
diff --git a/Documentation/devicetree/bindings/phy/nvidia,tegra124-xusb-padctl.txt b/Documentation/devicetree/bindings/phy/nvidia,tegra124-xusb-padctl.txt
new file mode 100644
index 0000000..0bf1ae2
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/nvidia,tegra124-xusb-padctl.txt
@@ -0,0 +1,733 @@
+Device tree binding for NVIDIA Tegra XUSB pad controller
+========================================================
+
+The Tegra XUSB pad controller manages a set of I/O lanes (with differential
+signals) which connect directly to pins/pads on the SoC package. Each lane
+is controlled by a HW block referred to as a "pad" in the Tegra hardware
+documentation. Each such "pad" may control either one or multiple lanes,
+and thus contains any logic common to all its lanes. Each lane can be
+separately configured and powered up.
+
+Some of the lanes are high-speed lanes, which can be used for PCIe, SATA or
+super-speed USB. Other lanes are for various types of low-speed, full-speed
+or high-speed USB (such as UTMI, ULPI and HSIC). The XUSB pad controller
+contains a software-configurable mux that sits between the I/O controller
+ports (e.g. PCIe) and the lanes.
+
+In addition to per-lane configuration, USB 3.0 ports may require additional
+settings on a per-board basis.
+
+Pads will be represented as children of the top-level XUSB pad controller
+device tree node. Each lane exposed by the pad will be represented by its
+own subnode and can be referenced by users of the lane using the standard
+PHY bindings, as described by the phy-bindings.txt file in this directory.
+
+The Tegra hardware documentation refers to the connection between the XUSB
+pad controller and the XUSB controller as "ports". This is confusing since
+"port" is typically used to denote the physical USB receptacle. The device
+tree binding in this document uses the term "port" to refer to the logical
+abstraction of the signals that are routed to a USB receptacle (i.e. a PHY
+for the USB signal, the VBUS power supply, the USB 2.0 companion port for
+USB 3.0 receptacles, ...).
+
+Required properties:
+--------------------
+- compatible: Must be:
+  - Tegra124: "nvidia,tegra124-xusb-padctl"
+  - Tegra132: "nvidia,tegra132-xusb-padctl", "nvidia,tegra124-xusb-padctl"
+  - Tegra210: "nvidia,tegra210-xusb-padctl"
+- reg: Physical base address and length of the controller's registers.
+- resets: Must contain an entry for each entry in reset-names.
+- reset-names: Must include the following entries:
+  - "padctl"
+
+
+Pad nodes:
+==========
+
+A required child node named "pads" contains a list of subnodes, one for each
+of the pads exposed by the XUSB pad controller. Each pad may need additional
+resources that can be referenced in its pad node.
+
+The "status" property is used to enable or disable the use of a pad. If set
+to "disabled", the pad will not be used on the given board. In order to use
+the pad and any of its lanes, this property must be set to "okay".
+
+For Tegra124 and Tegra132, the following pads exist: usb2, ulpi, hsic, pcie
+and sata. No extra resources are required for operation of these pads.
+
+For Tegra210, the following pads exist: usb2, hsic, pcie and sata. Below is
+a description of the properties of each pad.
+
+UTMI pad:
+---------
+
+Required properties:
+- clocks: Must contain an entry for each entry in clock-names.
+- clock-names: Must contain the following entries:
+  - "trk": phandle and specifier referring to the USB2 tracking clock
+
+HSIC pad:
+---------
+
+Required properties:
+- clocks: Must contain an entry for each entry in clock-names.
+- clock-names: Must contain the following entries:
+  - "trk": phandle and specifier referring to the HSIC tracking clock
+
+PCIe pad:
+---------
+
+Required properties:
+- clocks: Must contain an entry for each entry in clock-names.
+- clock-names: Must contain the following entries:
+  - "pll": phandle and specifier referring to the PLLE
+- resets: Must contain an entry for each entry in reset-names.
+- reset-names: Must contain the following entries:
+  - "phy": reset for the PCIe UPHY block
+
+SATA pad:
+---------
+
+Required properties:
+- resets: Must contain an entry for each entry in reset-names.
+- reset-names: Must contain the following entries:
+  - "phy": reset for the SATA UPHY block
+
+
+PHY nodes:
+==========
+
+Each pad node has a child named "lanes" that contains one or more children of
+its own, each representing one of the lanes controlled by the pad.
+
+Required properties:
+--------------------
+- status: Defines the operation status of the PHY. Valid values are:
+  - "disabled": the PHY is disabled
+  - "okay": the PHY is enabled
+- #phy-cells: Should be 0. Since each lane represents a single PHY, there is
+  no need for an additional specifier.
+- nvidia,function: The output function of the PHY. See below for a list of
+  valid functions per SoC generation.
+
+For Tegra124 and Tegra132, the list of valid PHY nodes is given below:
+- usb2: usb2-0, usb2-1, usb2-2
+  - functions: "snps", "xusb", "uart"
+- ulpi: ulpi-0
+  - functions: "snps", "xusb"
+- hsic: hsic-0, hsic-1
+  - functions: "snps", "xusb"
+- pcie: pcie-0, pcie-1, pcie-2, pcie-3, pcie-4
+  - functions: "pcie", "usb3-ss"
+- sata: sata-0
+  - functions: "usb3-ss", "sata"
+
+For Tegra210, the list of valid PHY nodes is given below:
+- utmi: utmi-0, utmi-1, utmi-2, utmi-3
+  - functions: "snps", "xusb", "uart"
+- hsic: hsic-0, hsic-1
+  - functions: "snps", "xusb"
+- pcie: pcie-0, pcie-1, pcie-2, pcie-3, pcie-4, pcie-5, pcie-6
+  - functions: "pcie-x1", "usb3-ss", "pcie-x4"
+- sata: sata-0
+  - functions: "usb3-ss", "sata"
+
+
+Port nodes:
+===========
+
+A required child node named "ports" contains a list of all the ports exposed
+by the XUSB pad controller. Per-port configuration is only required for USB.
+
+USB2 ports:
+-----------
+
+Required properties:
+- status: Defines the operation status of the port. Valid values are:
+  - "disabled": the port is disabled
+  - "okay": the port is enabled
+- mode: A string that determines the mode in which to run the port. Valid
+  values are:
+  - "host": for USB host mode
+  - "device": for USB device mode
+  - "otg": for USB OTG mode
+
+Optional properties:
+- nvidia,internal: A boolean property whose presence determines that a port
+  is internal. In the absence of this property the port is considered to be
+  external.
+- vbus-supply: phandle to a regulator supplying the VBUS voltage.
+
+ULPI ports:
+-----------
+
+Optional properties:
+- status: Defines the operation status of the port. Valid values are:
+  - "disabled": the port is disabled
+  - "okay": the port is enabled
+- nvidia,internal: A boolean property whose presence determines that a port
+  is internal. In the absence of this property the port is considered to be
+  external.
+- vbus-supply: phandle to a regulator supplying the VBUS voltage.
+
+HSIC ports:
+-----------
+
+Required properties:
+- status: Defines the operation status of the port. Valid values are:
+  - "disabled": the port is disabled
+  - "okay": the port is enabled
+
+Optional properties:
+- vbus-supply: phandle to a regulator supplying the VBUS voltage.
+
+Super-speed USB ports:
+----------------------
+
+Required properties:
+- status: Defines the operation status of the port. Valid values are:
+  - "disabled": the port is disabled
+  - "okay": the port is enabled
+- nvidia,usb2-companion: A single cell that specifies the physical port number
+  to map this super-speed USB port to. The range of valid port numbers varies
+  with the SoC generation:
+  - 0-2: for Tegra124 and Tegra132
+  - 0-3: for Tegra210
+
+Optional properties:
+- nvidia,internal: A boolean property whose presence determines that a port
+  is internal. In the absence of this property the port is considered to be
+  external.
+
+For Tegra124 and Tegra132, the XUSB pad controller exposes the following
+ports:
+- 3x USB2: usb2-0, usb2-1, usb2-2
+- 1x ULPI: ulpi-0
+- 2x HSIC: hsic-0, hsic-1
+- 2x super-speed USB: usb3-0, usb3-1
+
+For Tegra210, the XUSB pad controller exposes the following ports:
+- 4x USB2: usb2-0, usb2-1, usb2-2, usb2-3
+- 2x HSIC: hsic-0, hsic-1
+- 4x super-speed USB: usb3-0, usb3-1, usb3-2, usb3-3
+
+
+Examples:
+=========
+
+Tegra124 and Tegra132:
+----------------------
+
+SoC include:
+
+	padctl@7009f000 {
+		/* for Tegra124 */
+		compatible = "nvidia,tegra124-xusb-padctl";
+		/* for Tegra132 */
+		compatible = "nvidia,tegra132-xusb-padctl",
+			     "nvidia,tegra124-xusb-padctl";
+		reg = <0x0 0x7009f000 0x0 0x1000>;
+		resets = <&tegra_car 142>;
+		reset-names = "padctl";
+
+		pads {
+			usb2 {
+				status = "disabled";
+
+				lanes {
+					usb2-0 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+
+					usb2-1 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+
+					usb2-2 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+				};
+			};
+
+			ulpi {
+				status = "disabled";
+
+				lanes {
+					ulpi-0 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+				};
+			};
+
+			hsic {
+				status = "disabled";
+
+				lanes {
+					hsic-0 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+
+					hsic-1 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+				};
+			};
+
+			pcie {
+				status = "disabled";
+
+				lanes {
+					pcie-0 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+
+					pcie-1 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+
+					pcie-2 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+
+					pcie-3 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+
+					pcie-4 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+				};
+			};
+
+			sata {
+				status = "disabled";
+
+				lanes {
+					sata-0 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+				};
+			};
+		};
+
+		ports {
+			usb2-0 {
+				status = "disabled";
+			};
+
+			usb2-1 {
+				status = "disabled";
+			};
+
+			usb2-2 {
+				status = "disabled";
+			};
+
+			ulpi-0 {
+				status = "disabled";
+			};
+
+			hsic-0 {
+				status = "disabled";
+			};
+
+			hsic-1 {
+				status = "disabled";
+			};
+
+			usb3-0 {
+				status = "disabled";
+			};
+
+			usb3-1 {
+				status = "disabled";
+			};
+		};
+	};
+
+Board file:
+
+	padctl@7009f000 {
+		status = "okay";
+
+		pads {
+			usb2 {
+				status = "okay";
+
+				lanes {
+					usb2-0 {
+						nvidia,function = "xusb";
+						status = "okay";
+					};
+
+					usb2-1 {
+						nvidia,function = "xusb";
+						status = "okay";
+					};
+
+					usb2-2 {
+						nvidia,function = "xusb";
+						status = "okay";
+					};
+				};
+			};
+
+			pcie {
+				status = "okay";
+
+				lanes {
+					pcie-0 {
+						nvidia,function = "usb3-ss";
+						status = "okay";
+					};
+
+					pcie-2 {
+						nvidia,function = "pcie";
+						status = "okay";
+					};
+
+					pcie-4 {
+						nvidia,function = "pcie";
+						status = "okay";
+					};
+				};
+			};
+
+			sata {
+				status = "okay";
+
+				lanes {
+					sata-0 {
+						nvidia,function = "sata";
+						status = "okay";
+					};
+				};
+			};
+		};
+
+		ports {
+			/* Micro A/B */
+			usb2-0 {
+				status = "okay";
+				mode = "otg";
+			};
+
+			/* Mini PCIe */
+			usb2-1 {
+				status = "okay";
+				mode = "host";
+			};
+
+			/* USB3 */
+			usb2-2 {
+				status = "okay";
+				mode = "host";
+
+				vbus-supply = <&vdd_usb3_vbus>;
+			};
+
+			usb3-0 {
+				nvidia,port = <2>;
+				status = "okay";
+			};
+		};
+	};
+
+Tegra210:
+---------
+
+SoC include:
+
+	padctl@7009f000 {
+		compatible = "nvidia,tegra210-xusb-padctl";
+		reg = <0x0 0x7009f000 0x0 0x1000>;
+		resets = <&tegra_car 142>;
+		reset-names = "padctl";
+
+		status = "disabled";
+
+		pads {
+			usb2 {
+				clocks = <&tegra_car TEGRA210_CLK_USB2_TRK>;
+				clock-names = "trk";
+				status = "disabled";
+
+				lanes {
+					usb2-0 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+
+					usb2-1 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+
+					usb2-2 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+
+					usb2-3 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+				};
+			};
+
+			hsic {
+				clocks = <&tegra_car TEGRA210_CLK_HSIC_TRK>;
+				clock-names = "trk";
+				status = "disabled";
+
+				lanes {
+					hsic-0 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+
+					hsic-1 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+				};
+			};
+
+			pcie {
+				clocks = <&tegra_car TEGRA210_CLK_PLL_E>;
+				clock-names = "pll";
+				resets = <&tegra_car 205>;
+				reset-names = "phy";
+				status = "disabled";
+
+				lanes {
+					pcie-0 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+
+					pcie-1 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+
+					pcie-2 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+
+					pcie-3 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+
+					pcie-4 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+
+					pcie-5 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+
+					pcie-6 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+				};
+			};
+
+			sata {
+				clocks = <&tegra_car TEGRA210_CLK_PLL_E>;
+				clock-names = "pll";
+				resets = <&tegra_car 204>;
+				reset-names = "phy";
+				status = "disabled";
+
+				lanes {
+					sata-0 {
+						status = "disabled";
+						#phy-cells = <0>;
+					};
+				};
+			};
+		};
+
+		ports {
+			usb2-0 {
+				status = "disabled";
+			};
+
+			usb2-1 {
+				status = "disabled";
+			};
+
+			usb2-2 {
+				status = "disabled";
+			};
+
+			usb2-3 {
+				status = "disabled";
+			};
+
+			hsic-0 {
+				status = "disabled";
+			};
+
+			hsic-1 {
+				status = "disabled";
+			};
+
+			usb3-0 {
+				status = "disabled";
+			};
+
+			usb3-1 {
+				status = "disabled";
+			};
+
+			usb3-2 {
+				status = "disabled";
+			};
+
+			usb3-3 {
+				status = "disabled";
+			};
+		};
+	};
+
+Board file:
+
+	padctl@7009f000 {
+		status = "okay";
+
+		pads {
+			usb2 {
+				status = "okay";
+
+				lanes {
+					usb2-0 {
+						nvidia,function = "xusb";
+						status = "okay";
+					};
+
+					usb2-1 {
+						nvidia,function = "xusb";
+						status = "okay";
+					};
+
+					usb2-2 {
+						nvidia,function = "xusb";
+						status = "okay";
+					};
+
+					usb2-3 {
+						nvidia,function = "xusb";
+						status = "okay";
+					};
+				};
+			};
+
+			pcie {
+				status = "okay";
+
+				lanes {
+					pcie-0 {
+						nvidia,function = "pcie-x1";
+						status = "okay";
+					};
+
+					pcie-1 {
+						nvidia,function = "pcie-x4";
+						status = "okay";
+					};
+
+					pcie-2 {
+						nvidia,function = "pcie-x4";
+						status = "okay";
+					};
+
+					pcie-3 {
+						nvidia,function = "pcie-x4";
+						status = "okay";
+					};
+
+					pcie-4 {
+						nvidia,function = "pcie-x4";
+						status = "okay";
+					};
+
+					pcie-5 {
+						nvidia,function = "usb3-ss";
+						status = "okay";
+					};
+
+					pcie-6 {
+						nvidia,function = "usb3-ss";
+						status = "okay";
+					};
+				};
+			};
+
+			sata {
+				status = "okay";
+
+				lanes {
+					sata-0 {
+						nvidia,function = "sata";
+						status = "okay";
+					};
+				};
+			};
+		};
+
+		ports {
+			usb2-0 {
+				status = "okay";
+				mode = "otg";
+			};
+
+			usb2-1 {
+				status = "okay";
+				vbus-supply = <&vdd_5v0_rtl>;
+				mode = "host";
+			};
+
+			usb2-2 {
+				status = "okay";
+				vbus-supply = <&vdd_usb_vbus>;
+				mode = "host";
+			};
+
+			usb2-3 {
+				status = "okay";
+				mode = "host";
+			};
+
+			usb3-0 {
+				status = "okay";
+				nvidia,lanes = "pcie-6";
+				nvidia,port = <1>;
+			};
+
+			usb3-1 {
+				status = "okay";
+				nvidia,lanes = "pcie-5";
+				nvidia,port = <2>;
+			};
+		};
+	};
diff --git a/Documentation/devicetree/bindings/pinctrl/nvidia,tegra124-xusb-padctl.txt b/Documentation/devicetree/bindings/pinctrl/nvidia,tegra124-xusb-padctl.txt
index 30676de..8a6223d 100644
--- a/Documentation/devicetree/bindings/pinctrl/nvidia,tegra124-xusb-padctl.txt
+++ b/Documentation/devicetree/bindings/pinctrl/nvidia,tegra124-xusb-padctl.txt
@@ -1,6 +1,12 @@
 Device tree binding for NVIDIA Tegra XUSB pad controller
 ========================================================
 
+NOTE: It turns out that this binding isn't an accurate description of the XUSB
+pad controller. While the description is good enough for the functional subset
+required for PCIe and SATA, it lacks the flexibility to represent the features
+needed for USB. For the new binding, see ../phy/nvidia,tegra-xusb-padctl.txt.
+The binding described in this file is deprecated and should not be used.
+
 The Tegra XUSB pad controller manages a set of lanes, each of which can be
 assigned to one out of a set of different pads. Some of these pads have an
 associated PHY that must be powered up before the pad can be used.
diff --git a/Documentation/devicetree/bindings/usb/nvidia,tegra124-xusb.txt b/Documentation/devicetree/bindings/usb/nvidia,tegra124-xusb.txt
new file mode 100644
index 0000000..d28295a
--- /dev/null
+++ b/Documentation/devicetree/bindings/usb/nvidia,tegra124-xusb.txt
@@ -0,0 +1,120 @@
+NVIDIA Tegra xHCI controller
+============================
+
+The Tegra xHCI controller supports both USB2 and USB3 interfaces exposed by
+the Tegra XUSB pad controller.
+
+Required properties:
+--------------------
+- compatible: Must be:
+  - Tegra124: "nvidia,tegra124-xusb"
+  - Tegra132: "nvidia,tegra132-xusb", "nvidia,tegra124-xusb"
+  - Tegra210: "nvidia,tegra210-xusb"
+- reg: Must contain the base and length of the xHCI host registers, XUSB FPCI
+  registers and XUSB IPFS registers.
+- reg-names: Must contain the following entries:
+  - "hcd"
+  - "fpci"
+  - "ipfs"
+- interrupts: Must contain the xHCI host interrupt and the mailbox interrupt.
+- clocks: Must contain an entry for each entry in clock-names.
+  See ../clock/clock-bindings.txt for details.
+- clock-names: Must include the following entries:
+   - xusb_host
+   - xusb_host_src
+   - xusb_falcon_src
+   - xusb_ss
+   - xusb_ss_src
+   - xusb_ss_div2
+   - xusb_hs_src
+   - xusb_fs_src
+   - pll_u_480m
+   - clk_m
+   - pll_e
+- resets: Must contain an entry for each entry in reset-names.
+  See ../reset/reset.txt for details.
+- reset-names: Must include the following entries:
+  - xusb_host
+  - xusb_ss
+  - xusb_src
+  Note that xusb_src is the shared reset for xusb_{ss,hs,fs,falcon,host}_src.
+- nvidia,xusb-padctl: phandle to the XUSB pad controller that is used to
+  configure the USB pads used by the XHCI controller
+
+For Tegra124 and Tegra132:
+- avddio-pex-supply: PCIe/USB3 analog logic power supply. Must supply 1.05 V.
+- dvddio-pex-supply: PCIe/USB3 digital logic power supply. Must supply 1.05 V.
+- avdd-usb-supply: USB controller power supply. Must supply 3.3 V.
+- avdd-pll-utmip-supply: UTMI PLL power supply. Must supply 1.8 V.
+- avdd-pll-erefe-supply: PLLE reference PLL power supply. Must supply 1.05 V.
+- avdd-usb-ss-pll-supply: PCIe/USB3 PLL power supply. Must supply 1.05 V.
+- hvdd-usb-ss-supply: High-voltage PCIe/USB3 power supply. Must supply 3.3 V.
+- hvdd-usb-ss-pll-e-supply: High-voltage PLLE power supply. Must supply 3.3 V.
+
+For Tegra210:
+- dvddio-pex-supply: PCIe/USB3 analog logic power supply. Must supply 1.05 V.
+- hvddio-pex-supply: High-voltage PCIe/USB3 power supply. Must supply 1.8 V.
+- avdd-usb-supply: USB controller power supply. Must supply 3.3 V.
+- avdd-pll-utmip-supply: UTMI PLL power supply. Must supply 1.8 V.
+- avdd-pll-uerefe-supply: PLLE reference PLL power supply. Must supply 1.05 V.
+- dvdd-pex-pll-supply: PCIe/USB3 PLL power supply. Must supply 1.05 V.
+- hvdd-pex-pll-e-supply: High-voltage PLLE power supply. Must supply 1.8 V.
+
+Optional properties:
+--------------------
+- phys: Must contain an entry for each entry in phy-names.
+  See ../phy/phy-bindings.txt for details.
+- phy-names: Should include an entry for each PHY used by the controller. The
+  following PHYs are available:
+  - Tegra124: usb2-0, usb2-1, usb2-2, hsic-0, hsic-1, usb3-0, usb3-1
+  - Tegra132: usb2-0, usb2-1, usb2-2, hsic-0, hsic-1, usb3-0, usb3-1
+  - Tegra210: usb2-0, usb2-1, usb2-2, usb2-3, hsic-0, usb3-0, usb3-1, usb3-2,
+              usb3-3
+
+Example:
+--------
+
+	usb@0,70090000 {
+		compatible = "nvidia,tegra124-xusb";
+		reg = <0x0 0x70090000 0x0 0x8000>,
+		      <0x0 0x70098000 0x0 0x1000>,
+		      <0x0 0x70099000 0x0 0x1000>;
+		reg-names = "hcd", "fpci", "ipfs";
+
+		interrupts = <GIC_SPI 39 IRQ_TYPE_LEVEL_HIGH>,
+			     <GIC_SPI 40 IRQ_TYPE_LEVEL_HIGH>;
+
+		clocks = <&tegra_car TEGRA124_CLK_XUSB_HOST>,
+			 <&tegra_car TEGRA124_CLK_XUSB_HOST_SRC>,
+			 <&tegra_car TEGRA124_CLK_XUSB_FALCON_SRC>,
+			 <&tegra_car TEGRA124_CLK_XUSB_SS>,
+			 <&tegra_car TEGRA124_CLK_XUSB_SS_DIV2>,
+			 <&tegra_car TEGRA124_CLK_XUSB_SS_SRC>,
+			 <&tegra_car TEGRA124_CLK_XUSB_HS_SRC>,
+			 <&tegra_car TEGRA124_CLK_XUSB_FS_SRC>,
+			 <&tegra_car TEGRA124_CLK_PLL_U_480M>,
+			 <&tegra_car TEGRA124_CLK_CLK_M>,
+			 <&tegra_car TEGRA124_CLK_PLL_E>;
+		clock-names = "xusb_host", "xusb_host_src", "xusb_falcon_src",
+			      "xusb_ss", "xusb_ss_div2", "xusb_ss_src",
+			      "xusb_hs_src", "xusb_fs_src", "pll_u_480m",
+			      "clk_m", "pll_e";
+		resets = <&tegra_car 89>, <&tegra_car 156>, <&tegra_car 143>;
+		reset-names = "xusb_host", "xusb_ss", "xusb_src";
+
+		nvidia,xusb-padctl = <&padctl>;
+
+		phys = <&{/padctl@0,7009f000/pads/usb2/usb2-1}>, /* mini-PCIe USB */
+		       <&{/padctl@0,7009f000/pads/usb2/usb2-2}>, /* USB A */
+		       <&{/padctl@0,7009f000/pads/pcie/pcie-0}>; /* USB A */
+		phy-names = "utmi-1", "utmi-2", "usb3-0";
+
+		avddio-pex-supply = <&vdd_1v05_run>;
+		dvddio-pex-supply = <&vdd_1v05_run>;
+		avdd-usb-supply = <&vdd_3v3_lp0>;
+		avdd-pll-utmip-supply = <&vddio_1v8>;
+		avdd-pll-erefe-supply = <&avdd_1v05_run>;
+		avdd-usb-ss-pll-supply = <&vdd_1v05_run>;
+		hvdd-usb-ss-supply = <&vdd_3v3_lp0>;
+		hvdd-usb-ss-pll-e-supply = <&vdd_3v3_lp0>;
+	};
diff --git a/Documentation/phy.txt b/Documentation/phy.txt
index b388c5a..0aa994b 100644
--- a/Documentation/phy.txt
+++ b/Documentation/phy.txt
@@ -31,16 +31,28 @@
 dt boot case.
 
 #define of_phy_provider_register(dev, xlate)    \
-        __of_phy_provider_register((dev), THIS_MODULE, (xlate))
+        __of_phy_provider_register((dev), NULL, THIS_MODULE, (xlate))
 
 #define devm_of_phy_provider_register(dev, xlate)       \
-        __devm_of_phy_provider_register((dev), THIS_MODULE, (xlate))
+        __devm_of_phy_provider_register((dev), NULL, THIS_MODULE, (xlate))
 
 of_phy_provider_register and devm_of_phy_provider_register macros can be used to
 register the phy_provider and it takes device and of_xlate as
 arguments. For the dt boot case, all PHY providers should use one of the above
 2 macros to register the PHY provider.
 
+Often the device tree nodes associated with a PHY provider will contain a set
+of children that each represent a single PHY. Some bindings may nest the child
+nodes within extra levels for context and extensibility, in which case the low
+level of_phy_provider_register_full() and devm_of_phy_provider_register_full()
+macros can be used to override the node containing the children.
+
+#define of_phy_provider_register_full(dev, children, xlate) \
+	__of_phy_provider_register(dev, children, THIS_MODULE, xlate)
+
+#define devm_of_phy_provider_register_full(dev, children, xlate) \
+	__devm_of_phy_provider_register_full(dev, children, THIS_MODULE, xlate)
+
 void devm_of_phy_provider_unregister(struct device *dev,
 	struct phy_provider *phy_provider);
 void of_phy_provider_unregister(struct phy_provider *phy_provider);
diff --git a/drivers/clk/tegra/clk-tegra210.c b/drivers/clk/tegra/clk-tegra210.c
index 637041f..3d0edee 100644
--- a/drivers/clk/tegra/clk-tegra210.c
+++ b/drivers/clk/tegra/clk-tegra210.c
@@ -175,6 +175,19 @@
 #define UTMIP_PLL_CFG1_FORCE_PLL_ENABLE_POWERDOWN BIT(14)
 #define UTMIP_PLL_CFG1_FORCE_PLL_ACTIVE_POWERDOWN BIT(12)
 
+#define SATA_PLL_CFG0				0x490
+#define SATA_PLL_CFG0_PADPLL_RESET_SWCTL	BIT(0)
+#define SATA_PLL_CFG0_PADPLL_USE_LOCKDET	BIT(2)
+#define SATA_PLL_CFG0_PADPLL_SLEEP_IDDQ		BIT(13)
+#define SATA_PLL_CFG0_SEQ_ENABLE		BIT(24)
+
+#define XUSBIO_PLL_CFG0				0x51c
+#define XUSBIO_PLL_CFG0_PADPLL_RESET_SWCTL	BIT(0)
+#define XUSBIO_PLL_CFG0_CLK_ENABLE_SWCTL	BIT(2)
+#define XUSBIO_PLL_CFG0_PADPLL_USE_LOCKDET	BIT(6)
+#define XUSBIO_PLL_CFG0_PADPLL_SLEEP_IDDQ	BIT(13)
+#define XUSBIO_PLL_CFG0_SEQ_ENABLE		BIT(24)
+
 #define UTMIPLL_HW_PWRDN_CFG0			0x52c
 #define UTMIPLL_HW_PWRDN_CFG0_UTMIPLL_LOCK	BIT(31)
 #define UTMIPLL_HW_PWRDN_CFG0_SEQ_START_STATE	BIT(25)
@@ -416,6 +429,51 @@
 #define PLLU_MISC0_WRITE_MASK		0xbfffffff
 #define PLLU_MISC1_WRITE_MASK		0x00000007
 
+void tegra210_xusb_pll_hw_control_enable(void)
+{
+	u32 val;
+
+	val = readl_relaxed(clk_base + XUSBIO_PLL_CFG0);
+	val &= ~(XUSBIO_PLL_CFG0_CLK_ENABLE_SWCTL |
+		 XUSBIO_PLL_CFG0_PADPLL_RESET_SWCTL);
+	val |= XUSBIO_PLL_CFG0_PADPLL_USE_LOCKDET |
+	       XUSBIO_PLL_CFG0_PADPLL_SLEEP_IDDQ;
+	writel_relaxed(val, clk_base + XUSBIO_PLL_CFG0);
+}
+EXPORT_SYMBOL_GPL(tegra210_xusb_pll_hw_control_enable);
+
+void tegra210_xusb_pll_hw_sequence_start(void)
+{
+	u32 val;
+
+	val = readl_relaxed(clk_base + XUSBIO_PLL_CFG0);
+	val |= XUSBIO_PLL_CFG0_SEQ_ENABLE;
+	writel_relaxed(val, clk_base + XUSBIO_PLL_CFG0);
+}
+EXPORT_SYMBOL_GPL(tegra210_xusb_pll_hw_sequence_start);
+
+void tegra210_sata_pll_hw_control_enable(void)
+{
+	u32 val;
+
+	val = readl_relaxed(clk_base + SATA_PLL_CFG0);
+	val &= ~SATA_PLL_CFG0_PADPLL_RESET_SWCTL;
+	val |= SATA_PLL_CFG0_PADPLL_USE_LOCKDET |
+	       SATA_PLL_CFG0_PADPLL_SLEEP_IDDQ;
+	writel_relaxed(val, clk_base + SATA_PLL_CFG0);
+}
+EXPORT_SYMBOL_GPL(tegra210_sata_pll_hw_control_enable);
+
+void tegra210_sata_pll_hw_sequence_start(void)
+{
+	u32 val;
+
+	val = readl_relaxed(clk_base + SATA_PLL_CFG0);
+	val |= SATA_PLL_CFG0_SEQ_ENABLE;
+	writel_relaxed(val, clk_base + SATA_PLL_CFG0);
+}
+EXPORT_SYMBOL_GPL(tegra210_sata_pll_hw_sequence_start);
+
 static inline void _pll_misc_chk_default(void __iomem *base,
 					struct tegra_clk_pll_params *params,
 					u8 misc_num, u32 default_val, u32 mask)
diff --git a/drivers/pci/host/pci-tegra.c b/drivers/pci/host/pci-tegra.c
index 68d1f41..c388468 100644
--- a/drivers/pci/host/pci-tegra.c
+++ b/drivers/pci/host/pci-tegra.c
@@ -295,6 +295,7 @@
 	struct reset_control *afi_rst;
 	struct reset_control *pcie_xrst;
 
+	bool legacy_phy;
 	struct phy *phy;
 
 	struct tegra_msi msi;
@@ -311,11 +312,14 @@
 
 struct tegra_pcie_port {
 	struct tegra_pcie *pcie;
+	struct device_node *np;
 	struct list_head list;
 	struct resource regs;
 	void __iomem *base;
 	unsigned int index;
 	unsigned int lanes;
+
+	struct phy **phys;
 };
 
 struct tegra_pcie_bus {
@@ -860,6 +864,128 @@
 	return 0;
 }
 
+static int tegra_pcie_phy_disable(struct tegra_pcie *pcie)
+{
+	const struct tegra_pcie_soc_data *soc = pcie->soc_data;
+	u32 value;
+
+	/* disable TX/RX data */
+	value = pads_readl(pcie, PADS_CTL);
+	value &= ~(PADS_CTL_TX_DATA_EN_1L | PADS_CTL_RX_DATA_EN_1L);
+	pads_writel(pcie, value, PADS_CTL);
+
+	/* override IDDQ */
+	value = pads_readl(pcie, PADS_CTL);
+	value |= PADS_CTL_IDDQ_1L;
+	pads_writel(pcie, PADS_CTL, value);
+
+	/* reset PLL */
+	value = pads_readl(pcie, soc->pads_pll_ctl);
+	value &= ~PADS_PLL_CTL_RST_B4SM;
+	pads_writel(pcie, value, soc->pads_pll_ctl);
+
+	usleep_range(20, 100);
+
+	return 0;
+}
+
+static int tegra_pcie_port_phy_power_on(struct tegra_pcie_port *port)
+{
+	struct device *dev = port->pcie->dev;
+	unsigned int i;
+	int err;
+
+	for (i = 0; i < port->lanes; i++) {
+		err = phy_power_on(port->phys[i]);
+		if (err < 0) {
+			dev_err(dev, "failed to power on PHY#%u: %d\n", i,
+				err);
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+static int tegra_pcie_port_phy_power_off(struct tegra_pcie_port *port)
+{
+	struct device *dev = port->pcie->dev;
+	unsigned int i;
+	int err;
+
+	for (i = 0; i < port->lanes; i++) {
+		err = phy_power_off(port->phys[i]);
+		if (err < 0) {
+			dev_err(dev, "failed to power off PHY#%u: %d\n", i,
+				err);
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+static int tegra_pcie_phy_power_on(struct tegra_pcie *pcie)
+{
+	struct tegra_pcie_port *port;
+	int err;
+
+	if (pcie->legacy_phy) {
+		if (pcie->phy)
+			err = phy_power_on(pcie->phy);
+		else
+			err = tegra_pcie_phy_enable(pcie);
+
+		if (err < 0)
+			dev_err(pcie->dev, "failed to power on PHY: %d\n", err);
+
+		return err;
+	}
+
+	list_for_each_entry(port, &pcie->ports, list) {
+		err = tegra_pcie_port_phy_power_on(port);
+		if (err < 0) {
+			dev_err(pcie->dev,
+				"failed to power on PCIe port %u PHY: %d\n",
+				port->index, err);
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+static int tegra_pcie_phy_power_off(struct tegra_pcie *pcie)
+{
+	struct tegra_pcie_port *port;
+	int err;
+
+	if (pcie->legacy_phy) {
+		if (pcie->phy)
+			err = phy_power_off(pcie->phy);
+		else
+			err = tegra_pcie_phy_disable(pcie);
+
+		if (err < 0)
+			dev_err(pcie->dev, "failed to power off PHY: %d\n",
+				err);
+
+		return err;
+	}
+
+	list_for_each_entry(port, &pcie->ports, list) {
+		err = tegra_pcie_port_phy_power_off(port);
+		if (err < 0) {
+			dev_err(pcie->dev,
+				"failed to power off PCIe port %u PHY: %d\n",
+				port->index, err);
+			return err;
+		}
+	}
+
+	return 0;
+}
+
 static int tegra_pcie_enable_controller(struct tegra_pcie *pcie)
 {
 	const struct tegra_pcie_soc_data *soc = pcie->soc_data;
@@ -899,13 +1025,9 @@
 		afi_writel(pcie, value, AFI_FUSE);
 	}
 
-	if (!pcie->phy)
-		err = tegra_pcie_phy_enable(pcie);
-	else
-		err = phy_power_on(pcie->phy);
-
+	err = tegra_pcie_phy_power_on(pcie);
 	if (err < 0) {
-		dev_err(pcie->dev, "failed to power on PHY: %d\n", err);
+		dev_err(pcie->dev, "failed to power on PHY(s): %d\n", err);
 		return err;
 	}
 
@@ -942,9 +1064,9 @@
 
 	/* TODO: disable and unprepare clocks? */
 
-	err = phy_power_off(pcie->phy);
+	err = tegra_pcie_phy_power_off(pcie);
 	if (err < 0)
-		dev_warn(pcie->dev, "failed to power off PHY: %d\n", err);
+		dev_err(pcie->dev, "failed to power off PHY(s): %d\n", err);
 
 	reset_control_assert(pcie->pcie_xrst);
 	reset_control_assert(pcie->afi_rst);
@@ -1049,6 +1171,100 @@
 	return 0;
 }
 
+static int tegra_pcie_phys_get_legacy(struct tegra_pcie *pcie)
+{
+	int err;
+
+	pcie->phy = devm_phy_optional_get(pcie->dev, "pcie");
+	if (IS_ERR(pcie->phy)) {
+		err = PTR_ERR(pcie->phy);
+		dev_err(pcie->dev, "failed to get PHY: %d\n", err);
+		return err;
+	}
+
+	err = phy_init(pcie->phy);
+	if (err < 0) {
+		dev_err(pcie->dev, "failed to initialize PHY: %d\n", err);
+		return err;
+	}
+
+	pcie->legacy_phy = true;
+
+	return 0;
+}
+
+static struct phy *devm_of_phy_optional_get_index(struct device *dev,
+						  struct device_node *np,
+						  const char *consumer,
+						  unsigned int index)
+{
+	struct phy *phy;
+	char *name;
+
+	name = kasprintf(GFP_KERNEL, "%s-%u", consumer, index);
+	if (!name)
+		return ERR_PTR(-ENOMEM);
+
+	phy = devm_of_phy_get(dev, np, name);
+	kfree(name);
+
+	if (IS_ERR(phy) && PTR_ERR(phy) == -ENODEV)
+		phy = NULL;
+
+	return phy;
+}
+
+static int tegra_pcie_port_get_phys(struct tegra_pcie_port *port)
+{
+	struct device *dev = port->pcie->dev;
+	struct phy *phy;
+	unsigned int i;
+	int err;
+
+	port->phys = devm_kcalloc(dev, sizeof(phy), port->lanes, GFP_KERNEL);
+	if (!port->phys)
+		return -ENOMEM;
+
+	for (i = 0; i < port->lanes; i++) {
+		phy = devm_of_phy_optional_get_index(dev, port->np, "pcie", i);
+		if (IS_ERR(phy)) {
+			dev_err(dev, "failed to get PHY#%u: %ld\n", i,
+				PTR_ERR(phy));
+			return PTR_ERR(phy);
+		}
+
+		err = phy_init(phy);
+		if (err < 0) {
+			dev_err(dev, "failed to initialize PHY#%u: %d\n", i,
+				err);
+			return err;
+		}
+
+		port->phys[i] = phy;
+	}
+
+	return 0;
+}
+
+static int tegra_pcie_phys_get(struct tegra_pcie *pcie)
+{
+	const struct tegra_pcie_soc_data *soc = pcie->soc_data;
+	struct device_node *np = pcie->dev->of_node;
+	struct tegra_pcie_port *port;
+	int err;
+
+	if (!soc->has_gen2 || of_find_property(np, "phys", NULL) != NULL)
+		return tegra_pcie_phys_get_legacy(pcie);
+
+	list_for_each_entry(port, &pcie->ports, list) {
+		err = tegra_pcie_port_get_phys(port);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
 static int tegra_pcie_get_resources(struct tegra_pcie *pcie)
 {
 	struct platform_device *pdev = to_platform_device(pcie->dev);
@@ -1067,16 +1283,9 @@
 		return err;
 	}
 
-	pcie->phy = devm_phy_optional_get(pcie->dev, "pcie");
-	if (IS_ERR(pcie->phy)) {
-		err = PTR_ERR(pcie->phy);
-		dev_err(&pdev->dev, "failed to get PHY: %d\n", err);
-		return err;
-	}
-
-	err = phy_init(pcie->phy);
+	err = tegra_pcie_phys_get(pcie);
 	if (err < 0) {
-		dev_err(&pdev->dev, "failed to initialize PHY: %d\n", err);
+		dev_err(&pdev->dev, "failed to get PHYs: %d\n", err);
 		return err;
 	}
 
@@ -1752,6 +1961,7 @@
 		rp->index = index;
 		rp->lanes = value;
 		rp->pcie = pcie;
+		rp->np = port;
 
 		rp->base = devm_ioremap_resource(pcie->dev, &rp->regs);
 		if (IS_ERR(rp->base))
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index 26566db..27e5f6e 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -421,4 +421,6 @@
 	  Enable this to support the Broadcom Cygnus PCIe PHY.
 	  If unsure, say N.
 
+source "drivers/phy/tegra/Kconfig"
+
 endmenu
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index 24596a9..d4f06e6 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -52,3 +52,5 @@
 obj-$(CONFIG_PHY_BRCMSTB_SATA)		+= phy-brcmstb-sata.o
 obj-$(CONFIG_PHY_PISTACHIO_USB)		+= phy-pistachio-usb.o
 obj-$(CONFIG_PHY_CYGNUS_PCIE)		+= phy-bcm-cygnus-pcie.o
+
+obj-$(CONFIG_ARCH_TEGRA) += tegra/
diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c
index e7e574d..b72e9a3 100644
--- a/drivers/phy/phy-core.c
+++ b/drivers/phy/phy-core.c
@@ -141,7 +141,7 @@
 		if (phy_provider->dev->of_node == node)
 			return phy_provider;
 
-		for_each_child_of_node(phy_provider->dev->of_node, child)
+		for_each_child_of_node(phy_provider->children, child)
 			if (child == node)
 				return phy_provider;
 	}
@@ -811,24 +811,59 @@
 /**
  * __of_phy_provider_register() - create/register phy provider with the framework
  * @dev: struct device of the phy provider
+ * @children: device node containing children (if different from dev->of_node)
  * @owner: the module owner containing of_xlate
  * @of_xlate: function pointer to obtain phy instance from phy provider
  *
  * Creates struct phy_provider from dev and of_xlate function pointer.
  * This is used in the case of dt boot for finding the phy instance from
  * phy provider.
+ *
+ * If the PHY provider doesn't nest children directly but uses a separate
+ * child node to contain the individual children, the @children parameter
+ * can be used to override the default. If NULL, the default (dev->of_node)
+ * will be used. If non-NULL, the device node must be a child (or further
+ * descendant) of dev->of_node. Otherwise an ERR_PTR()-encoded -EINVAL
+ * error code is returned.
  */
 struct phy_provider *__of_phy_provider_register(struct device *dev,
-	struct module *owner, struct phy * (*of_xlate)(struct device *dev,
-	struct of_phandle_args *args))
+	struct device_node *children, struct module *owner,
+	struct phy * (*of_xlate)(struct device *dev,
+				 struct of_phandle_args *args))
 {
 	struct phy_provider *phy_provider;
 
+	/*
+	 * If specified, the device node containing the children must itself
+	 * be the provider's device node or a child (or further descendant)
+	 * thereof.
+	 */
+	if (children) {
+		struct device_node *parent = of_node_get(children), *next;
+
+		while (parent) {
+			if (parent == dev->of_node)
+				break;
+
+			next = of_get_parent(parent);
+			of_node_put(parent);
+			parent = next;
+		}
+
+		if (!parent)
+			return ERR_PTR(-EINVAL);
+
+		of_node_put(parent);
+	} else {
+		children = dev->of_node;
+	}
+
 	phy_provider = kzalloc(sizeof(*phy_provider), GFP_KERNEL);
 	if (!phy_provider)
 		return ERR_PTR(-ENOMEM);
 
 	phy_provider->dev = dev;
+	phy_provider->children = of_node_get(children);
 	phy_provider->owner = owner;
 	phy_provider->of_xlate = of_xlate;
 
@@ -854,8 +889,9 @@
  * on the devres data, then, devres data is freed.
  */
 struct phy_provider *__devm_of_phy_provider_register(struct device *dev,
-	struct module *owner, struct phy * (*of_xlate)(struct device *dev,
-	struct of_phandle_args *args))
+	struct device_node *children, struct module *owner,
+	struct phy * (*of_xlate)(struct device *dev,
+				 struct of_phandle_args *args))
 {
 	struct phy_provider **ptr, *phy_provider;
 
@@ -863,7 +899,8 @@
 	if (!ptr)
 		return ERR_PTR(-ENOMEM);
 
-	phy_provider = __of_phy_provider_register(dev, owner, of_xlate);
+	phy_provider = __of_phy_provider_register(dev, children, owner,
+						  of_xlate);
 	if (!IS_ERR(phy_provider)) {
 		*ptr = phy_provider;
 		devres_add(dev, ptr);
@@ -888,6 +925,7 @@
 
 	mutex_lock(&phy_provider_mutex);
 	list_del(&phy_provider->list);
+	of_node_put(phy_provider->children);
 	kfree(phy_provider);
 	mutex_unlock(&phy_provider_mutex);
 }
diff --git a/drivers/phy/tegra/Kconfig b/drivers/phy/tegra/Kconfig
new file mode 100644
index 0000000..a3b1de9
--- /dev/null
+++ b/drivers/phy/tegra/Kconfig
@@ -0,0 +1,8 @@
+config PHY_TEGRA_XUSB
+	tristate "NVIDIA Tegra XUSB pad controller driver"
+	depends on ARCH_TEGRA
+	help
+	  Choose this option if you have an NVIDIA Tegra SoC.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called phy-tegra-xusb.
diff --git a/drivers/phy/tegra/Makefile b/drivers/phy/tegra/Makefile
new file mode 100644
index 0000000..8985892
--- /dev/null
+++ b/drivers/phy/tegra/Makefile
@@ -0,0 +1,6 @@
+obj-$(CONFIG_PHY_TEGRA_XUSB) += phy-tegra-xusb.o
+
+phy-tegra-xusb-y += xusb.o
+phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_124_SOC) += xusb-tegra124.o
+phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_132_SOC) += xusb-tegra124.o
+phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_210_SOC) += xusb-tegra210.o
diff --git a/drivers/phy/tegra/xusb-tegra124.c b/drivers/phy/tegra/xusb-tegra124.c
new file mode 100644
index 0000000..1199572
--- /dev/null
+++ b/drivers/phy/tegra/xusb-tegra124.c
@@ -0,0 +1,1752 @@
+/*
+ * Copyright (c) 2014, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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/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/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+
+#include <soc/tegra/fuse.h>
+
+#include "xusb.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_INTERNAL(x) (1 << (((x) * 4) + 3))
+#define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_SHIFT(x) ((x) * 4)
+#define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(x) (0x7 << ((x) * 4))
+#define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(x, v) (((v) & 0x7) << ((x) * 4))
+#define XUSB_PADCTL_SS_PORT_MAP_PORT_MAP_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)
+#define XUSB_PADCTL_IOPHY_PLL_P0_CTL1_REFCLK_SEL_MASK (0xf << 12)
+#define XUSB_PADCTL_IOPHY_PLL_P0_CTL1_PLL_RST (1 << 1)
+
+#define XUSB_PADCTL_IOPHY_PLL_P0_CTL2 0x044
+#define XUSB_PADCTL_IOPHY_PLL_P0_CTL2_REFCLKBUF_EN (1 << 6)
+#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_CDR_CNTL_VAL 0x24
+#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_EQ_VAL 0xf070
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_SHIFT 4
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_MASK 0xf
+#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_VAL 0xf
+
+#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_USB3_PAD_CTL4_DFE_CNTL_VAL 0x002008ee
+
+#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_CTL2_SPARE_IN_VAL 0x1
+
+#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_LS_RSLEW_VAL(x) ((x) ? 0x0 : 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_SLEW_VAL 0x0e
+#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_DISCON_LEVEL_VAL 0x5
+#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_USB3_PAD_MUX 0x134
+#define XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(x) (1 << (1 + (x)))
+#define XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(x) (1 << (6 + (x)))
+
+#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 tegra124_xusb_fuse_calibration {
+	u32 hs_curr_level[3];
+	u32 hs_iref_cap;
+	u32 hs_term_range_adj;
+	u32 hs_squelch_level;
+};
+
+struct tegra124_xusb_padctl {
+	struct tegra_xusb_padctl base;
+
+	struct tegra124_xusb_fuse_calibration fuse;
+};
+
+static inline struct tegra124_xusb_padctl *
+to_tegra124_xusb_padctl(struct tegra_xusb_padctl *padctl)
+{
+	return container_of(padctl, struct tegra124_xusb_padctl, base);
+}
+
+static int tegra124_xusb_padctl_enable(struct tegra_xusb_padctl *padctl)
+{
+	u32 value;
+
+	mutex_lock(&padctl->lock);
+
+	if (padctl->enable++ > 0)
+		goto out;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+	value &= ~XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN;
+	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_AUX_MUX_LP0_CLAMP_EN_EARLY;
+	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_AUX_MUX_LP0_VCORE_DOWN;
+	padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+out:
+	mutex_unlock(&padctl->lock);
+	return 0;
+}
+
+static int tegra124_xusb_padctl_disable(struct tegra_xusb_padctl *padctl)
+{
+	u32 value;
+
+	mutex_lock(&padctl->lock);
+
+	if (WARN_ON(padctl->enable == 0))
+		goto out;
+
+	if (--padctl->enable > 0)
+		goto out;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+	value |= XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_VCORE_DOWN;
+	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_AUX_MUX_LP0_CLAMP_EN_EARLY;
+	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_AUX_MUX_LP0_CLAMP_EN;
+	padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+out:
+	mutex_unlock(&padctl->lock);
+	return 0;
+}
+
+static int tegra124_usb3_save_context(struct tegra_xusb_padctl *padctl,
+				      unsigned int index)
+{
+	struct tegra_xusb_usb3_port *port;
+	struct tegra_xusb_lane *lane;
+	u32 value, offset;
+
+	port = tegra_xusb_find_usb3_port(padctl, index);
+	if (!port)
+		return -ENODEV;
+
+	port->context_saved = true;
+	lane = port->base.lane;
+
+	if (lane->pad == padctl->pcie)
+		offset = XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL6(lane->index);
+	else
+		offset = XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL6;
+
+	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;
+	port->tap1 = 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;
+	port->amp = value & XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_AMP_MASK;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_USB3_PADX_CTL4(index));
+	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 |= (port->tap1 <<
+		  XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_SHIFT) |
+		 (port->amp <<
+		  XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_SHIFT);
+	padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_USB3_PADX_CTL4(index));
+
+	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;
+	port->ctle_g = 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;
+	port->ctle_z = value &
+		XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_G_Z_MASK;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_USB3_PADX_CTL2(index));
+	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 |= (port->ctle_g <<
+		  XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_SHIFT) |
+		 (port->ctle_z <<
+		  XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_SHIFT);
+	padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_USB3_PADX_CTL2(index));
+
+	return 0;
+}
+
+static int tegra124_hsic_set_idle(struct tegra_xusb_padctl *padctl,
+				  unsigned int index, bool idle)
+{
+	u32 value;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL1(index));
+
+	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(index));
+
+	return 0;
+}
+
+#define TEGRA124_LANE(_name, _offset, _shift, _mask, _type)		\
+	{								\
+		.name = _name,						\
+		.offset = _offset,					\
+		.shift = _shift,					\
+		.mask = _mask,						\
+		.num_funcs = ARRAY_SIZE(tegra124_##_type##_functions),	\
+		.funcs = tegra124_##_type##_functions,			\
+	}
+
+static const char * const tegra124_usb2_functions[] = {
+	"snps",
+	"xusb",
+	"uart",
+};
+
+static const struct tegra_xusb_lane_soc tegra124_usb2_lanes[] = {
+	TEGRA124_LANE("usb2-0", 0x004,  0, 0x3, usb2),
+	TEGRA124_LANE("usb2-1", 0x004,  2, 0x3, usb2),
+	TEGRA124_LANE("usb2-2", 0x004,  4, 0x3, usb2),
+};
+
+static struct tegra_xusb_lane *
+tegra124_usb2_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np,
+			 unsigned int index)
+{
+	struct tegra_xusb_usb2_lane *usb2;
+	int err;
+
+	usb2 = kzalloc(sizeof(*usb2), GFP_KERNEL);
+	if (!usb2)
+		return ERR_PTR(-ENOMEM);
+
+	INIT_LIST_HEAD(&usb2->base.list);
+	usb2->base.soc = &pad->soc->lanes[index];
+	usb2->base.index = index;
+	usb2->base.pad = pad;
+	usb2->base.np = np;
+
+	err = tegra_xusb_lane_parse_dt(&usb2->base, np);
+	if (err < 0) {
+		kfree(usb2);
+		return ERR_PTR(err);
+	}
+
+	return &usb2->base;
+}
+
+static void tegra124_usb2_lane_remove(struct tegra_xusb_lane *lane)
+{
+	struct tegra_xusb_usb2_lane *usb2 = to_usb2_lane(lane);
+
+	kfree(usb2);
+}
+
+static const struct tegra_xusb_lane_ops tegra124_usb2_lane_ops = {
+	.probe = tegra124_usb2_lane_probe,
+	.remove = tegra124_usb2_lane_remove,
+};
+
+static int tegra124_usb2_phy_init(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+	return tegra124_xusb_padctl_enable(lane->pad->padctl);
+}
+
+static int tegra124_usb2_phy_exit(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+	return tegra124_xusb_padctl_disable(lane->pad->padctl);
+}
+
+static int tegra124_usb2_phy_power_on(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_usb2_lane *usb2 = to_usb2_lane(lane);
+	struct tegra_xusb_usb2_pad *pad = to_usb2_pad(lane->pad);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	struct tegra124_xusb_padctl *priv;
+	struct tegra_xusb_usb2_port *port;
+	unsigned int index = lane->index;
+	u32 value;
+	int err;
+
+	port = tegra_xusb_find_usb2_port(padctl, index);
+	if (!port) {
+		dev_err(&phy->dev, "no port found for USB2 lane %u\n", index);
+		return -ENODEV;
+	}
+
+	priv = to_tegra124_xusb_padctl(padctl);
+
+	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 |= (priv->fuse.hs_squelch_level <<
+		  XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_SHIFT) |
+		 (XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_VAL <<
+		  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(index));
+	value |= XUSB_PADCTL_USB2_PORT_CAP_HOST <<
+		XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_SHIFT(index);
+	padctl_writel(padctl, value, XUSB_PADCTL_USB2_PORT_CAP);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index));
+	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 |= (priv->fuse.hs_curr_level[index] +
+		  usb2->hs_curr_level_offset) <<
+		XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_SHIFT;
+	value |= XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_VAL <<
+		XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_SHIFT;
+	value |= XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_VAL(index) <<
+		XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_SHIFT;
+	padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index));
+
+	value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
+	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 |= (priv->fuse.hs_term_range_adj <<
+		  XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_SHIFT) |
+		 (priv->fuse.hs_iref_cap <<
+		  XUSB_PADCTL_USB2_OTG_PAD_CTL1_HS_IREF_CAP_SHIFT);
+	padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
+
+	err = regulator_enable(port->supply);
+	if (err)
+		return err;
+
+	mutex_lock(&pad->lock);
+
+	if (pad->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(&pad->lock);
+	return 0;
+}
+
+static int tegra124_usb2_phy_power_off(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_usb2_pad *pad = to_usb2_pad(lane->pad);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	struct tegra_xusb_usb2_port *port;
+	u32 value;
+
+	port = tegra_xusb_find_usb2_port(padctl, lane->index);
+	if (!port) {
+		dev_err(&phy->dev, "no port found for USB2 lane %u\n",
+			lane->index);
+		return -ENODEV;
+	}
+
+	mutex_lock(&pad->lock);
+
+	if (WARN_ON(pad->enable == 0))
+		goto out;
+
+	if (--pad->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:
+	regulator_disable(port->supply);
+	mutex_unlock(&pad->lock);
+	return 0;
+}
+
+static const struct phy_ops tegra124_usb2_phy_ops = {
+	.init = tegra124_usb2_phy_init,
+	.exit = tegra124_usb2_phy_exit,
+	.power_on = tegra124_usb2_phy_power_on,
+	.power_off = tegra124_usb2_phy_power_off,
+	.owner = THIS_MODULE,
+};
+
+static struct tegra_xusb_pad *
+tegra124_usb2_pad_probe(struct tegra_xusb_padctl *padctl,
+			const struct tegra_xusb_pad_soc *soc,
+			struct device_node *np)
+{
+	struct tegra_xusb_usb2_pad *usb2;
+	struct tegra_xusb_pad *pad;
+	int err;
+
+	usb2 = kzalloc(sizeof(*usb2), GFP_KERNEL);
+	if (!usb2)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&usb2->lock);
+
+	pad = &usb2->base;
+	pad->ops = &tegra124_usb2_lane_ops;
+	pad->soc = soc;
+
+	err = tegra_xusb_pad_init(pad, padctl, np);
+	if (err < 0) {
+		kfree(usb2);
+		goto out;
+	}
+
+	err = tegra_xusb_pad_register(pad, &tegra124_usb2_phy_ops);
+	if (err < 0)
+		goto unregister;
+
+	dev_set_drvdata(&pad->dev, pad);
+
+	return pad;
+
+unregister:
+	device_unregister(&pad->dev);
+out:
+	return ERR_PTR(err);
+}
+
+static void tegra124_usb2_pad_remove(struct tegra_xusb_pad *pad)
+{
+	struct tegra_xusb_usb2_pad *usb2 = to_usb2_pad(pad);
+
+	kfree(usb2);
+}
+
+static const struct tegra_xusb_pad_ops tegra124_usb2_ops = {
+	.probe = tegra124_usb2_pad_probe,
+	.remove = tegra124_usb2_pad_remove,
+};
+
+static const struct tegra_xusb_pad_soc tegra124_usb2_pad = {
+	.name = "usb2",
+	.num_lanes = ARRAY_SIZE(tegra124_usb2_lanes),
+	.lanes = tegra124_usb2_lanes,
+	.ops = &tegra124_usb2_ops,
+};
+
+static const char * const tegra124_ulpi_functions[] = {
+	"snps",
+	"xusb",
+};
+
+static const struct tegra_xusb_lane_soc tegra124_ulpi_lanes[] = {
+	TEGRA124_LANE("ulpi-0", 0x004, 12, 0x1, ulpi),
+};
+
+static struct tegra_xusb_lane *
+tegra124_ulpi_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np,
+			 unsigned int index)
+{
+	struct tegra_xusb_ulpi_lane *ulpi;
+	int err;
+
+	ulpi = kzalloc(sizeof(*ulpi), GFP_KERNEL);
+	if (!ulpi)
+		return ERR_PTR(-ENOMEM);
+
+	INIT_LIST_HEAD(&ulpi->base.list);
+	ulpi->base.soc = &pad->soc->lanes[index];
+	ulpi->base.index = index;
+	ulpi->base.pad = pad;
+	ulpi->base.np = np;
+
+	err = tegra_xusb_lane_parse_dt(&ulpi->base, np);
+	if (err < 0) {
+		kfree(ulpi);
+		return ERR_PTR(err);
+	}
+
+	return &ulpi->base;
+}
+
+static void tegra124_ulpi_lane_remove(struct tegra_xusb_lane *lane)
+{
+	struct tegra_xusb_ulpi_lane *ulpi = to_ulpi_lane(lane);
+
+	kfree(ulpi);
+}
+
+static const struct tegra_xusb_lane_ops tegra124_ulpi_lane_ops = {
+	.probe = tegra124_ulpi_lane_probe,
+	.remove = tegra124_ulpi_lane_remove,
+};
+
+static int tegra124_ulpi_phy_init(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+	return tegra124_xusb_padctl_enable(lane->pad->padctl);
+}
+
+static int tegra124_ulpi_phy_exit(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+	return tegra124_xusb_padctl_disable(lane->pad->padctl);
+}
+
+static int tegra124_ulpi_phy_power_on(struct phy *phy)
+{
+	return 0;
+}
+
+static int tegra124_ulpi_phy_power_off(struct phy *phy)
+{
+	return 0;
+}
+
+static const struct phy_ops tegra124_ulpi_phy_ops = {
+	.init = tegra124_ulpi_phy_init,
+	.exit = tegra124_ulpi_phy_exit,
+	.power_on = tegra124_ulpi_phy_power_on,
+	.power_off = tegra124_ulpi_phy_power_off,
+	.owner = THIS_MODULE,
+};
+
+static struct tegra_xusb_pad *
+tegra124_ulpi_pad_probe(struct tegra_xusb_padctl *padctl,
+			const struct tegra_xusb_pad_soc *soc,
+			struct device_node *np)
+{
+	struct tegra_xusb_ulpi_pad *ulpi;
+	struct tegra_xusb_pad *pad;
+	int err;
+
+	ulpi = kzalloc(sizeof(*ulpi), GFP_KERNEL);
+	if (!ulpi)
+		return ERR_PTR(-ENOMEM);
+
+	pad = &ulpi->base;
+	pad->ops = &tegra124_ulpi_lane_ops;
+	pad->soc = soc;
+
+	err = tegra_xusb_pad_init(pad, padctl, np);
+	if (err < 0) {
+		kfree(ulpi);
+		goto out;
+	}
+
+	err = tegra_xusb_pad_register(pad, &tegra124_ulpi_phy_ops);
+	if (err < 0)
+		goto unregister;
+
+	dev_set_drvdata(&pad->dev, pad);
+
+	return pad;
+
+unregister:
+	device_unregister(&pad->dev);
+out:
+	return ERR_PTR(err);
+}
+
+static void tegra124_ulpi_pad_remove(struct tegra_xusb_pad *pad)
+{
+	struct tegra_xusb_ulpi_pad *ulpi = to_ulpi_pad(pad);
+
+	kfree(ulpi);
+}
+
+static const struct tegra_xusb_pad_ops tegra124_ulpi_ops = {
+	.probe = tegra124_ulpi_pad_probe,
+	.remove = tegra124_ulpi_pad_remove,
+};
+
+static const struct tegra_xusb_pad_soc tegra124_ulpi_pad = {
+	.name = "ulpi",
+	.num_lanes = ARRAY_SIZE(tegra124_ulpi_lanes),
+	.lanes = tegra124_ulpi_lanes,
+	.ops = &tegra124_ulpi_ops,
+};
+
+static const char * const tegra124_hsic_functions[] = {
+	"snps",
+	"xusb",
+};
+
+static const struct tegra_xusb_lane_soc tegra124_hsic_lanes[] = {
+	TEGRA124_LANE("hsic-0", 0x004, 14, 0x1, hsic),
+	TEGRA124_LANE("hsic-1", 0x004, 15, 0x1, hsic),
+};
+
+static struct tegra_xusb_lane *
+tegra124_hsic_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np,
+			 unsigned int index)
+{
+	struct tegra_xusb_hsic_lane *hsic;
+	int err;
+
+	hsic = kzalloc(sizeof(*hsic), GFP_KERNEL);
+	if (!hsic)
+		return ERR_PTR(-ENOMEM);
+
+	INIT_LIST_HEAD(&hsic->base.list);
+	hsic->base.soc = &pad->soc->lanes[index];
+	hsic->base.index = index;
+	hsic->base.pad = pad;
+	hsic->base.np = np;
+
+	err = tegra_xusb_lane_parse_dt(&hsic->base, np);
+	if (err < 0) {
+		kfree(hsic);
+		return ERR_PTR(err);
+	}
+
+	return &hsic->base;
+}
+
+static void tegra124_hsic_lane_remove(struct tegra_xusb_lane *lane)
+{
+	struct tegra_xusb_hsic_lane *hsic = to_hsic_lane(lane);
+
+	kfree(hsic);
+}
+
+static const struct tegra_xusb_lane_ops tegra124_hsic_lane_ops = {
+	.probe = tegra124_hsic_lane_probe,
+	.remove = tegra124_hsic_lane_remove,
+};
+
+static int tegra124_hsic_phy_init(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+	return tegra124_xusb_padctl_enable(lane->pad->padctl);
+}
+
+static int tegra124_hsic_phy_exit(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+	return tegra124_xusb_padctl_disable(lane->pad->padctl);
+}
+
+static int tegra124_hsic_phy_power_on(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_hsic_lane *hsic = to_hsic_lane(lane);
+	struct tegra_xusb_hsic_pad *pad = to_hsic_pad(lane->pad);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	unsigned int index = lane->index;
+	u32 value;
+	int err;
+
+	err = regulator_enable(pad->supply);
+	if (err)
+		return err;
+
+	padctl_writel(padctl, hsic->strobe_trim,
+		      XUSB_PADCTL_HSIC_STRB_TRIM_CONTROL);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL1(index));
+
+	if (hsic->auto_term)
+		value |= XUSB_PADCTL_HSIC_PAD_CTL1_AUTO_TERM_EN;
+	else
+		value &= ~XUSB_PADCTL_HSIC_PAD_CTL1_AUTO_TERM_EN;
+
+	padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL1(index));
+
+	value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL0(index));
+	value &= ~((XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_MASK <<
+		    XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_SHIFT) |
+		   (XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_MASK <<
+		    XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_SHIFT) |
+		   (XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_MASK <<
+		    XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_SHIFT) |
+		   (XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_MASK <<
+		    XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_SHIFT));
+	value |= (hsic->tx_rtune_n <<
+		  XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_SHIFT) |
+		(hsic->tx_rtune_p <<
+		  XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_SHIFT) |
+		(hsic->tx_rslew_n <<
+		 XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_SHIFT) |
+		(hsic->tx_rslew_p <<
+		 XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_SHIFT);
+	padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL0(index));
+
+	value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL2(index));
+	value &= ~((XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_MASK <<
+		    XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT) |
+		   (XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_MASK <<
+		    XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT));
+	value |= (hsic->rx_strobe_trim <<
+		  XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT) |
+		(hsic->rx_data_trim <<
+		 XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT);
+	padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL2(index));
+
+	value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL1(index));
+	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(index));
+
+	return 0;
+}
+
+static int tegra124_hsic_phy_power_off(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_hsic_pad *pad = to_hsic_pad(lane->pad);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	unsigned int index = lane->index;
+	u32 value;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL1(index));
+	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(index));
+
+	regulator_disable(pad->supply);
+
+	return 0;
+}
+
+static const struct phy_ops tegra124_hsic_phy_ops = {
+	.init = tegra124_hsic_phy_init,
+	.exit = tegra124_hsic_phy_exit,
+	.power_on = tegra124_hsic_phy_power_on,
+	.power_off = tegra124_hsic_phy_power_off,
+	.owner = THIS_MODULE,
+};
+
+static struct tegra_xusb_pad *
+tegra124_hsic_pad_probe(struct tegra_xusb_padctl *padctl,
+			const struct tegra_xusb_pad_soc *soc,
+			struct device_node *np)
+{
+	struct tegra_xusb_hsic_pad *hsic;
+	struct tegra_xusb_pad *pad;
+	int err;
+
+	hsic = kzalloc(sizeof(*hsic), GFP_KERNEL);
+	if (!hsic)
+		return ERR_PTR(-ENOMEM);
+
+	pad = &hsic->base;
+	pad->ops = &tegra124_hsic_lane_ops;
+	pad->soc = soc;
+
+	err = tegra_xusb_pad_init(pad, padctl, np);
+	if (err < 0) {
+		kfree(hsic);
+		goto out;
+	}
+
+	err = tegra_xusb_pad_register(pad, &tegra124_hsic_phy_ops);
+	if (err < 0)
+		goto unregister;
+
+	dev_set_drvdata(&pad->dev, pad);
+
+	return pad;
+
+unregister:
+	device_unregister(&pad->dev);
+out:
+	return ERR_PTR(err);
+}
+
+static void tegra124_hsic_pad_remove(struct tegra_xusb_pad *pad)
+{
+	struct tegra_xusb_hsic_pad *hsic = to_hsic_pad(pad);
+
+	kfree(hsic);
+}
+
+static const struct tegra_xusb_pad_ops tegra124_hsic_ops = {
+	.probe = tegra124_hsic_pad_probe,
+	.remove = tegra124_hsic_pad_remove,
+};
+
+static const struct tegra_xusb_pad_soc tegra124_hsic_pad = {
+	.name = "hsic",
+	.num_lanes = ARRAY_SIZE(tegra124_hsic_lanes),
+	.lanes = tegra124_hsic_lanes,
+	.ops = &tegra124_hsic_ops,
+};
+
+static const char * const tegra124_pcie_functions[] = {
+	"pcie",
+	"usb3-ss",
+	"sata",
+};
+
+static const struct tegra_xusb_lane_soc tegra124_pcie_lanes[] = {
+	TEGRA124_LANE("pcie-0", 0x134, 16, 0x3, pcie),
+	TEGRA124_LANE("pcie-1", 0x134, 18, 0x3, pcie),
+	TEGRA124_LANE("pcie-2", 0x134, 20, 0x3, pcie),
+	TEGRA124_LANE("pcie-3", 0x134, 22, 0x3, pcie),
+	TEGRA124_LANE("pcie-4", 0x134, 24, 0x3, pcie),
+};
+
+static struct tegra_xusb_lane *
+tegra124_pcie_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np,
+			 unsigned int index)
+{
+	struct tegra_xusb_pcie_lane *pcie;
+	int err;
+
+	pcie = kzalloc(sizeof(*pcie), GFP_KERNEL);
+	if (!pcie)
+		return ERR_PTR(-ENOMEM);
+
+	INIT_LIST_HEAD(&pcie->base.list);
+	pcie->base.soc = &pad->soc->lanes[index];
+	pcie->base.index = index;
+	pcie->base.pad = pad;
+	pcie->base.np = np;
+
+	err = tegra_xusb_lane_parse_dt(&pcie->base, np);
+	if (err < 0) {
+		kfree(pcie);
+		return ERR_PTR(err);
+	}
+
+	return &pcie->base;
+}
+
+static void tegra124_pcie_lane_remove(struct tegra_xusb_lane *lane)
+{
+	struct tegra_xusb_pcie_lane *pcie = to_pcie_lane(lane);
+
+	kfree(pcie);
+}
+
+static const struct tegra_xusb_lane_ops tegra124_pcie_lane_ops = {
+	.probe = tegra124_pcie_lane_probe,
+	.remove = tegra124_pcie_lane_remove,
+};
+
+static int tegra124_pcie_phy_init(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+	return tegra124_xusb_padctl_enable(lane->pad->padctl);
+}
+
+static int tegra124_pcie_phy_exit(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+	return tegra124_xusb_padctl_disable(lane->pad->padctl);
+}
+
+static int tegra124_pcie_phy_power_on(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	unsigned long timeout;
+	int err = -ETIMEDOUT;
+	u32 value;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_P0_CTL1);
+	value &= ~XUSB_PADCTL_IOPHY_PLL_P0_CTL1_REFCLK_SEL_MASK;
+	padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_P0_CTL1);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_P0_CTL2);
+	value |= XUSB_PADCTL_IOPHY_PLL_P0_CTL2_REFCLKBUF_EN |
+		 XUSB_PADCTL_IOPHY_PLL_P0_CTL2_TXCLKREF_EN |
+		 XUSB_PADCTL_IOPHY_PLL_P0_CTL2_TXCLKREF_SEL;
+	padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_P0_CTL2);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_P0_CTL1);
+	value |= XUSB_PADCTL_IOPHY_PLL_P0_CTL1_PLL_RST;
+	padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_P0_CTL1);
+
+	timeout = jiffies + msecs_to_jiffies(50);
+
+	while (time_before(jiffies, timeout)) {
+		value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_P0_CTL1);
+		if (value & XUSB_PADCTL_IOPHY_PLL_P0_CTL1_PLL0_LOCKDET) {
+			err = 0;
+			break;
+		}
+
+		usleep_range(100, 200);
+	}
+
+	value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX);
+	value |= XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(lane->index);
+	padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX);
+
+	return err;
+}
+
+static int tegra124_pcie_phy_power_off(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	u32 value;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX);
+	value &= ~XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(lane->index);
+	padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_P0_CTL1);
+	value &= ~XUSB_PADCTL_IOPHY_PLL_P0_CTL1_PLL_RST;
+	padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_P0_CTL1);
+
+	return 0;
+}
+
+static const struct phy_ops tegra124_pcie_phy_ops = {
+	.init = tegra124_pcie_phy_init,
+	.exit = tegra124_pcie_phy_exit,
+	.power_on = tegra124_pcie_phy_power_on,
+	.power_off = tegra124_pcie_phy_power_off,
+	.owner = THIS_MODULE,
+};
+
+static struct tegra_xusb_pad *
+tegra124_pcie_pad_probe(struct tegra_xusb_padctl *padctl,
+			const struct tegra_xusb_pad_soc *soc,
+			struct device_node *np)
+{
+	struct tegra_xusb_pcie_pad *pcie;
+	struct tegra_xusb_pad *pad;
+	int err;
+
+	pcie = kzalloc(sizeof(*pcie), GFP_KERNEL);
+	if (!pcie)
+		return ERR_PTR(-ENOMEM);
+
+	pad = &pcie->base;
+	pad->ops = &tegra124_pcie_lane_ops;
+	pad->soc = soc;
+
+	err = tegra_xusb_pad_init(pad, padctl, np);
+	if (err < 0) {
+		kfree(pcie);
+		goto out;
+	}
+
+	err = tegra_xusb_pad_register(pad, &tegra124_pcie_phy_ops);
+	if (err < 0)
+		goto unregister;
+
+	dev_set_drvdata(&pad->dev, pad);
+
+	return pad;
+
+unregister:
+	device_unregister(&pad->dev);
+out:
+	return ERR_PTR(err);
+}
+
+static void tegra124_pcie_pad_remove(struct tegra_xusb_pad *pad)
+{
+	struct tegra_xusb_pcie_pad *pcie = to_pcie_pad(pad);
+
+	kfree(pcie);
+}
+
+static const struct tegra_xusb_pad_ops tegra124_pcie_ops = {
+	.probe = tegra124_pcie_pad_probe,
+	.remove = tegra124_pcie_pad_remove,
+};
+
+static const struct tegra_xusb_pad_soc tegra124_pcie_pad = {
+	.name = "pcie",
+	.num_lanes = ARRAY_SIZE(tegra124_pcie_lanes),
+	.lanes = tegra124_pcie_lanes,
+	.ops = &tegra124_pcie_ops,
+};
+
+static const struct tegra_xusb_lane_soc tegra124_sata_lanes[] = {
+	TEGRA124_LANE("sata-0", 0x134, 26, 0x3, pcie),
+};
+
+static struct tegra_xusb_lane *
+tegra124_sata_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np,
+			 unsigned int index)
+{
+	struct tegra_xusb_sata_lane *sata;
+	int err;
+
+	sata = kzalloc(sizeof(*sata), GFP_KERNEL);
+	if (!sata)
+		return ERR_PTR(-ENOMEM);
+
+	INIT_LIST_HEAD(&sata->base.list);
+	sata->base.soc = &pad->soc->lanes[index];
+	sata->base.index = index;
+	sata->base.pad = pad;
+	sata->base.np = np;
+
+	err = tegra_xusb_lane_parse_dt(&sata->base, np);
+	if (err < 0) {
+		kfree(sata);
+		return ERR_PTR(err);
+	}
+
+	return &sata->base;
+}
+
+static void tegra124_sata_lane_remove(struct tegra_xusb_lane *lane)
+{
+	struct tegra_xusb_sata_lane *sata = to_sata_lane(lane);
+
+	kfree(sata);
+}
+
+static const struct tegra_xusb_lane_ops tegra124_sata_lane_ops = {
+	.probe = tegra124_sata_lane_probe,
+	.remove = tegra124_sata_lane_remove,
+};
+
+static int tegra124_sata_phy_init(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+	return tegra124_xusb_padctl_enable(lane->pad->padctl);
+}
+
+static int tegra124_sata_phy_exit(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+	return tegra124_xusb_padctl_disable(lane->pad->padctl);
+}
+
+static int tegra124_sata_phy_power_on(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	unsigned long timeout;
+	int err = -ETIMEDOUT;
+	u32 value;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1);
+	value &= ~XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1_IDDQ_OVRD;
+	value &= ~XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1_IDDQ;
+	padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+	value &= ~XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_PWR_OVRD;
+	value &= ~XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_IDDQ;
+	padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+	value |= XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL1_MODE;
+	padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+	value |= XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_RST;
+	padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+
+	timeout = jiffies + msecs_to_jiffies(50);
+
+	while (time_before(jiffies, timeout)) {
+		value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+		if (value & XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL1_LOCKDET) {
+			err = 0;
+			break;
+		}
+
+		usleep_range(100, 200);
+	}
+
+	value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX);
+	value |= XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(lane->index);
+	padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX);
+
+	return err;
+}
+
+static int tegra124_sata_phy_power_off(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	u32 value;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX);
+	value &= ~XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(lane->index);
+	padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+	value &= ~XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_RST;
+	padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+	value &= ~XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL1_MODE;
+	padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+	value |= XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_PWR_OVRD;
+	value |= XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_IDDQ;
+	padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1);
+	value |= ~XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1_IDDQ_OVRD;
+	value |= ~XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1_IDDQ;
+	padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1);
+
+	return 0;
+}
+
+static const struct phy_ops tegra124_sata_phy_ops = {
+	.init = tegra124_sata_phy_init,
+	.exit = tegra124_sata_phy_exit,
+	.power_on = tegra124_sata_phy_power_on,
+	.power_off = tegra124_sata_phy_power_off,
+	.owner = THIS_MODULE,
+};
+
+static struct tegra_xusb_pad *
+tegra124_sata_pad_probe(struct tegra_xusb_padctl *padctl,
+			const struct tegra_xusb_pad_soc *soc,
+			struct device_node *np)
+{
+	struct tegra_xusb_sata_pad *sata;
+	struct tegra_xusb_pad *pad;
+	int err;
+
+	sata = kzalloc(sizeof(*sata), GFP_KERNEL);
+	if (!sata)
+		return ERR_PTR(-ENOMEM);
+
+	pad = &sata->base;
+	pad->ops = &tegra124_sata_lane_ops;
+	pad->soc = soc;
+
+	err = tegra_xusb_pad_init(pad, padctl, np);
+	if (err < 0) {
+		kfree(sata);
+		goto out;
+	}
+
+	err = tegra_xusb_pad_register(pad, &tegra124_sata_phy_ops);
+	if (err < 0)
+		goto unregister;
+
+	dev_set_drvdata(&pad->dev, pad);
+
+	return pad;
+
+unregister:
+	device_unregister(&pad->dev);
+out:
+	return ERR_PTR(err);
+}
+
+static void tegra124_sata_pad_remove(struct tegra_xusb_pad *pad)
+{
+	struct tegra_xusb_sata_pad *sata = to_sata_pad(pad);
+
+	kfree(sata);
+}
+
+static const struct tegra_xusb_pad_ops tegra124_sata_ops = {
+	.probe = tegra124_sata_pad_probe,
+	.remove = tegra124_sata_pad_remove,
+};
+
+static const struct tegra_xusb_pad_soc tegra124_sata_pad = {
+	.name = "sata",
+	.num_lanes = ARRAY_SIZE(tegra124_sata_lanes),
+	.lanes = tegra124_sata_lanes,
+	.ops = &tegra124_sata_ops,
+};
+
+static const struct tegra_xusb_pad_soc *tegra124_pads[] = {
+	&tegra124_usb2_pad,
+	&tegra124_ulpi_pad,
+	&tegra124_hsic_pad,
+	&tegra124_pcie_pad,
+	&tegra124_sata_pad,
+};
+
+static int tegra124_usb2_port_enable(struct tegra_xusb_port *port)
+{
+	return 0;
+}
+
+static void tegra124_usb2_port_disable(struct tegra_xusb_port *port)
+{
+}
+
+static struct tegra_xusb_lane *
+tegra124_usb2_port_map(struct tegra_xusb_port *port)
+{
+	return tegra_xusb_find_lane(port->padctl, "usb2", port->index);
+}
+
+static const struct tegra_xusb_port_ops tegra124_usb2_port_ops = {
+	.enable = tegra124_usb2_port_enable,
+	.disable = tegra124_usb2_port_disable,
+	.map = tegra124_usb2_port_map,
+};
+
+static int tegra124_ulpi_port_enable(struct tegra_xusb_port *port)
+{
+	return 0;
+}
+
+static void tegra124_ulpi_port_disable(struct tegra_xusb_port *port)
+{
+}
+
+static struct tegra_xusb_lane *
+tegra124_ulpi_port_map(struct tegra_xusb_port *port)
+{
+	return tegra_xusb_find_lane(port->padctl, "ulpi", port->index);
+}
+
+static const struct tegra_xusb_port_ops tegra124_ulpi_port_ops = {
+	.enable = tegra124_ulpi_port_enable,
+	.disable = tegra124_ulpi_port_disable,
+	.map = tegra124_ulpi_port_map,
+};
+
+static int tegra124_hsic_port_enable(struct tegra_xusb_port *port)
+{
+	return 0;
+}
+
+static void tegra124_hsic_port_disable(struct tegra_xusb_port *port)
+{
+}
+
+static struct tegra_xusb_lane *
+tegra124_hsic_port_map(struct tegra_xusb_port *port)
+{
+	return tegra_xusb_find_lane(port->padctl, "hsic", port->index);
+}
+
+static const struct tegra_xusb_port_ops tegra124_hsic_port_ops = {
+	.enable = tegra124_hsic_port_enable,
+	.disable = tegra124_hsic_port_disable,
+	.map = tegra124_hsic_port_map,
+};
+
+static int tegra124_usb3_port_enable(struct tegra_xusb_port *port)
+{
+	struct tegra_xusb_usb3_port *usb3 = to_usb3_port(port);
+	struct tegra_xusb_padctl *padctl = port->padctl;
+	struct tegra_xusb_lane *lane = usb3->base.lane;
+	unsigned int index = port->index, offset;
+	int ret = 0;
+	u32 value;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_MAP);
+
+	if (!usb3->internal)
+		value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(index);
+	else
+		value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(index);
+
+	value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(index);
+	value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(index, usb3->port);
+	padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_MAP);
+
+	/*
+	 * TODO: move this code into the PCIe/SATA PHY ->power_on() callbacks
+	 * and conditionalize based on mux function? This seems to work, but
+	 * might not be the exact proper sequence.
+	 */
+	value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_USB3_PADX_CTL2(index));
+	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 |= (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_VAL <<
+		  XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_SHIFT) |
+		 (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_VAL <<
+		  XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_SHIFT) |
+		 (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_VAL <<
+		  XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_SHIFT);
+
+	if (usb3->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 |= (usb3->ctle_g <<
+			  XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_SHIFT) |
+			 (usb3->ctle_z <<
+			  XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_SHIFT);
+	}
+
+	padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_USB3_PADX_CTL2(index));
+
+	value = XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_VAL;
+
+	if (usb3->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 |= (usb3->tap1 <<
+			  XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_SHIFT) |
+			 (usb3->amp <<
+			  XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_SHIFT);
+	}
+
+	padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_USB3_PADX_CTL4(index));
+
+	if (lane->pad == padctl->pcie)
+		offset = XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL2(lane->index);
+	else
+		offset = XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL2;
+
+	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 |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_VAL <<
+		XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_SHIFT;
+	padctl_writel(padctl, value, offset);
+
+	if (lane->pad == padctl->pcie)
+		offset = XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL5(lane->index);
+	else
+		offset = XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL5;
+
+	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->pad == padctl->sata) {
+		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_VCORE_DOWN(index);
+	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(index);
+	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(index);
+	padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+	return ret;
+}
+
+static void tegra124_usb3_port_disable(struct tegra_xusb_port *port)
+{
+	struct tegra_xusb_padctl *padctl = port->padctl;
+	u32 value;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM);
+	value |= XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_CLAMP_EN_EARLY(port->index);
+	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(port->index);
+	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->index);
+	padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_MAP);
+	value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(port->index);
+	value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(port->index, 0x7);
+	padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_MAP);
+}
+
+static const struct tegra_xusb_lane_map tegra124_usb3_map[] = {
+	{ 0, "pcie", 0 },
+	{ 1, "pcie", 1 },
+	{ 1, "sata", 0 },
+	{ 0, NULL,   0 },
+};
+
+static struct tegra_xusb_lane *
+tegra124_usb3_port_map(struct tegra_xusb_port *port)
+{
+	return tegra_xusb_port_find_lane(port, tegra124_usb3_map, "usb3-ss");
+}
+
+static const struct tegra_xusb_port_ops tegra124_usb3_port_ops = {
+	.enable = tegra124_usb3_port_enable,
+	.disable = tegra124_usb3_port_disable,
+	.map = tegra124_usb3_port_map,
+};
+
+static int
+tegra124_xusb_read_fuse_calibration(struct tegra124_xusb_fuse_calibration *fuse)
+{
+	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 < ARRAY_SIZE(fuse->hs_curr_level); i++) {
+		fuse->hs_curr_level[i] =
+			(value >> FUSE_SKU_CALIB_HS_CURR_LEVEL_PADX_SHIFT(i)) &
+			FUSE_SKU_CALIB_HS_CURR_LEVEL_PAD_MASK;
+	}
+	fuse->hs_iref_cap =
+		(value >> FUSE_SKU_CALIB_HS_IREF_CAP_SHIFT) &
+		FUSE_SKU_CALIB_HS_IREF_CAP_MASK;
+	fuse->hs_term_range_adj =
+		(value >> FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_SHIFT) &
+		FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_MASK;
+	fuse->hs_squelch_level =
+		(value >> FUSE_SKU_CALIB_HS_SQUELCH_LEVEL_SHIFT) &
+		FUSE_SKU_CALIB_HS_SQUELCH_LEVEL_MASK;
+
+	return 0;
+}
+
+static struct tegra_xusb_padctl *
+tegra124_xusb_padctl_probe(struct device *dev,
+			   const struct tegra_xusb_padctl_soc *soc)
+{
+	struct tegra124_xusb_padctl *padctl;
+	int err;
+
+	padctl = devm_kzalloc(dev, sizeof(*padctl), GFP_KERNEL);
+	if (!padctl)
+		return ERR_PTR(-ENOMEM);
+
+	padctl->base.dev = dev;
+	padctl->base.soc = soc;
+
+	err = tegra124_xusb_read_fuse_calibration(&padctl->fuse);
+	if (err < 0)
+		return ERR_PTR(err);
+
+	return &padctl->base;
+}
+
+static void tegra124_xusb_padctl_remove(struct tegra_xusb_padctl *padctl)
+{
+}
+
+static const struct tegra_xusb_padctl_ops tegra124_xusb_padctl_ops = {
+	.probe = tegra124_xusb_padctl_probe,
+	.remove = tegra124_xusb_padctl_remove,
+	.usb3_save_context = tegra124_usb3_save_context,
+	.hsic_set_idle = tegra124_hsic_set_idle,
+};
+
+const struct tegra_xusb_padctl_soc tegra124_xusb_padctl_soc = {
+	.num_pads = ARRAY_SIZE(tegra124_pads),
+	.pads = tegra124_pads,
+	.ports = {
+		.usb2 = {
+			.ops = &tegra124_usb2_port_ops,
+			.count = 3,
+		},
+		.ulpi = {
+			.ops = &tegra124_ulpi_port_ops,
+			.count = 1,
+		},
+		.hsic = {
+			.ops = &tegra124_hsic_port_ops,
+			.count = 2,
+		},
+		.usb3 = {
+			.ops = &tegra124_usb3_port_ops,
+			.count = 2,
+		},
+	},
+	.ops = &tegra124_xusb_padctl_ops,
+};
+EXPORT_SYMBOL_GPL(tegra124_xusb_padctl_soc);
+
+MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
+MODULE_DESCRIPTION("NVIDIA Tegra 124 XUSB Pad Controller driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/tegra/xusb-tegra210.c b/drivers/phy/tegra/xusb-tegra210.c
new file mode 100644
index 0000000..9d0689e
--- /dev/null
+++ b/drivers/phy/tegra/xusb-tegra210.c
@@ -0,0 +1,2045 @@
+/*
+ * Copyright (c) 2014, NVIDIA CORPORATION.  All rights reserved.
+ * Copyright (C) 2015 Google, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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/clk.h>
+#include <linux/clk/tegra.h>
+#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/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+
+#include <soc/tegra/fuse.h>
+
+#include "xusb.h"
+
+#define FUSE_SKU_CALIB_HS_CURR_LEVEL_PADX_SHIFT(x) \
+					((x) ? (11 + ((x) - 1) * 6) : 0)
+#define FUSE_SKU_CALIB_HS_CURR_LEVEL_PAD_MASK 0x3f
+#define FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_SHIFT 7
+#define FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_MASK 0xf
+
+#define FUSE_USB_CALIB_EXT_RPD_CTRL_SHIFT 0
+#define FUSE_USB_CALIB_EXT_RPD_CTRL_MASK 0x1f
+
+#define XUSB_PADCTL_USB2_PAD_MUX 0x004
+#define XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_SHIFT 16
+#define XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_MASK 0x3
+#define XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_XUSB 0x1
+#define XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_SHIFT 18
+#define XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_MASK 0x3
+#define XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_XUSB 0x1
+
+#define XUSB_PADCTL_USB2_PORT_CAP 0x008
+#define XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_HOST(x) (0x1 << ((x) * 4))
+#define XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_MASK(x) (0x3 << ((x) * 4))
+
+#define XUSB_PADCTL_SS_PORT_MAP 0x014
+#define XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(x) (1 << (((x) * 5) + 4))
+#define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_SHIFT(x) ((x) * 5)
+#define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(x) (0x7 << ((x) * 5))
+#define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(x, v) (((v) & 0x7) << ((x) * 5))
+
+#define XUSB_PADCTL_ELPG_PROGRAM1 0x024
+#define XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_VCORE_DOWN (1 << 31)
+#define XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN_EARLY (1 << 30)
+#define XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN (1 << 29)
+#define XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_VCORE_DOWN(x) (1 << (2 + (x) * 3))
+#define XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN_EARLY(x) \
+							(1 << (1 + (x) * 3))
+#define XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN(x) (1 << ((x) * 3))
+
+#define XUSB_PADCTL_USB3_PAD_MUX 0x028
+#define XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(x) (1 << (1 + (x)))
+#define XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(x) (1 << (8 + (x)))
+
+#define XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPADX_CTL1(x) (0x084 + (x) * 0x40)
+#define XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD_CTL1_VREG_LEV_SHIFT 7
+#define XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD_CTL1_VREG_LEV_MASK 0x3
+#define XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD_CTL1_VREG_FIX18 (1 << 6)
+
+#define XUSB_PADCTL_USB2_OTG_PADX_CTL0(x) (0x088 + (x) * 0x40)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD_ZI (1 << 29)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD2 (1 << 27)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD (1 << 26)
+#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) (0x08c + (x) * 0x40)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_RPD_CTRL_SHIFT 26
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_RPD_CTRL_MASK 0x1f
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_SHIFT 3
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_MASK 0xf
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DR (1 << 2)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DISC_OVRD (1 << 1)
+#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_CHRP_OVRD (1 << 0)
+
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0 0x284
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD (1 << 11)
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_SHIFT 3
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_MASK 0x7
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_VAL 0x7
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_SHIFT 0
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_MASK 0x7
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_VAL 0x2
+
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1 0x288
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_PD_TRK (1 << 26)
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_SHIFT 19
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_MASK 0x7f
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_VAL 0x0a
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_SHIFT 12
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_MASK 0x7f
+#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_VAL 0x1e
+
+#define XUSB_PADCTL_HSIC_PADX_CTL0(x) (0x300 + (x) * 0x20)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_RPU_STROBE (1 << 18)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_RPU_DATA1 (1 << 17)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_RPU_DATA0 (1 << 16)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_RPD_STROBE (1 << 15)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA1 (1 << 14)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA0 (1 << 13)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_STROBE (1 << 9)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_DATA1 (1 << 8)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_DATA0 (1 << 7)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_STROBE (1 << 6)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_DATA1 (1 << 5)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_DATA0 (1 << 4)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_STROBE (1 << 3)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_DATA1 (1 << 2)
+#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_DATA0 (1 << 1)
+
+#define XUSB_PADCTL_HSIC_PADX_CTL1(x) (0x304 + (x) * 0x20)
+#define XUSB_PADCTL_HSIC_PAD_CTL1_TX_RTUNEP_SHIFT 0
+#define XUSB_PADCTL_HSIC_PAD_CTL1_TX_RTUNEP_MASK 0xf
+
+#define XUSB_PADCTL_HSIC_PADX_CTL2(x) (0x308 + (x) * 0x20)
+#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT 8
+#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_MASK 0xf
+#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT 0
+#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_MASK 0xff
+
+#define XUSB_PADCTL_HSIC_PAD_TRK_CTL 0x340
+#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_PD_TRK (1 << 19)
+#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_SHIFT 12
+#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_MASK 0x7f
+#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_VAL 0x0a
+#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_SHIFT 5
+#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_MASK 0x7f
+#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_VAL 0x1e
+
+#define XUSB_PADCTL_HSIC_STRB_TRIM_CONTROL 0x344
+
+#define XUSB_PADCTL_UPHY_PLL_P0_CTL1 0x360
+#define XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SHIFT 20
+#define XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_MASK 0xff
+#define XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_USB_VAL 0x19
+#define XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SATA_VAL 0x1e
+#define XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_MDIV_SHIFT 16
+#define XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_MDIV_MASK 0x3
+#define XUSB_PADCTL_UPHY_PLL_CTL1_LOCKDET_STATUS (1 << 15)
+#define XUSB_PADCTL_UPHY_PLL_CTL1_PWR_OVRD (1 << 4)
+#define XUSB_PADCTL_UPHY_PLL_CTL1_ENABLE (1 << 3)
+#define XUSB_PADCTL_UPHY_PLL_CTL1_SLEEP_SHIFT 1
+#define XUSB_PADCTL_UPHY_PLL_CTL1_SLEEP_MASK 0x3
+#define XUSB_PADCTL_UPHY_PLL_CTL1_IDDQ (1 << 0)
+
+#define XUSB_PADCTL_UPHY_PLL_P0_CTL2 0x364
+#define XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_SHIFT 4
+#define XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_MASK 0xffffff
+#define XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_VAL 0x136
+#define XUSB_PADCTL_UPHY_PLL_CTL2_CAL_OVRD (1 << 2)
+#define XUSB_PADCTL_UPHY_PLL_CTL2_CAL_DONE (1 << 1)
+#define XUSB_PADCTL_UPHY_PLL_CTL2_CAL_EN (1 << 0)
+
+#define XUSB_PADCTL_UPHY_PLL_P0_CTL4 0x36c
+#define XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_EN (1 << 15)
+#define XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT 12
+#define XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_MASK 0x3
+#define XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_USB_VAL 0x2
+#define XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SATA_VAL 0x0
+#define XUSB_PADCTL_UPHY_PLL_CTL4_REFCLKBUF_EN (1 << 8)
+#define XUSB_PADCTL_UPHY_PLL_CTL4_REFCLK_SEL_SHIFT 4
+#define XUSB_PADCTL_UPHY_PLL_CTL4_REFCLK_SEL_MASK 0xf
+
+#define XUSB_PADCTL_UPHY_PLL_P0_CTL5 0x370
+#define XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_SHIFT 16
+#define XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_MASK 0xff
+#define XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_VAL 0x2a
+
+#define XUSB_PADCTL_UPHY_PLL_P0_CTL8 0x37c
+#define XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_DONE (1 << 31)
+#define XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_OVRD (1 << 15)
+#define XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_CLK_EN (1 << 13)
+#define XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_EN (1 << 12)
+
+#define XUSB_PADCTL_UPHY_MISC_PAD_PX_CTL1(x) (0x460 + (x) * 0x40)
+#define XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_SHIFT 20
+#define XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_MASK 0x3
+#define XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_VAL 0x1
+#define XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_TERM_EN BIT(18)
+#define XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_MODE_OVRD BIT(13)
+
+#define XUSB_PADCTL_UPHY_PLL_S0_CTL1 0x860
+
+#define XUSB_PADCTL_UPHY_PLL_S0_CTL2 0x864
+
+#define XUSB_PADCTL_UPHY_PLL_S0_CTL4 0x86c
+
+#define XUSB_PADCTL_UPHY_PLL_S0_CTL5 0x870
+
+#define XUSB_PADCTL_UPHY_PLL_S0_CTL8 0x87c
+
+#define XUSB_PADCTL_UPHY_MISC_PAD_S0_CTL1 0x960
+
+#define XUSB_PADCTL_UPHY_USB3_PADX_ECTL1(x) (0xa60 + (x) * 0x40)
+#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_SHIFT 16
+#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_MASK 0x3
+#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_VAL 0x2
+
+#define XUSB_PADCTL_UPHY_USB3_PADX_ECTL2(x) (0xa64 + (x) * 0x40)
+#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_SHIFT 0
+#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_MASK 0xffff
+#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_VAL 0x00fc
+
+#define XUSB_PADCTL_UPHY_USB3_PADX_ECTL3(x) (0xa68 + (x) * 0x40)
+#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL3_RX_DFE_VAL 0xc0077f1f
+
+#define XUSB_PADCTL_UPHY_USB3_PADX_ECTL4(x) (0xa6c + (x) * 0x40)
+#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_SHIFT 16
+#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_MASK 0xffff
+#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_VAL 0x01c7
+
+#define XUSB_PADCTL_UPHY_USB3_PADX_ECTL6(x) (0xa74 + (x) * 0x40)
+#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL6_RX_EQ_CTRL_H_VAL 0xfcf01368
+
+struct tegra210_xusb_fuse_calibration {
+	u32 hs_curr_level[4];
+	u32 hs_term_range_adj;
+	u32 rpd_ctrl;
+};
+
+struct tegra210_xusb_padctl {
+	struct tegra_xusb_padctl base;
+
+	struct tegra210_xusb_fuse_calibration fuse;
+};
+
+static inline struct tegra210_xusb_padctl *
+to_tegra210_xusb_padctl(struct tegra_xusb_padctl *padctl)
+{
+	return container_of(padctl, struct tegra210_xusb_padctl, base);
+}
+
+/* must be called under padctl->lock */
+static int tegra210_pex_uphy_enable(struct tegra_xusb_padctl *padctl)
+{
+	struct tegra_xusb_pcie_pad *pcie = to_pcie_pad(padctl->pcie);
+	unsigned long timeout;
+	u32 value;
+	int err;
+
+	if (pcie->enable > 0) {
+		pcie->enable++;
+		return 0;
+	}
+
+	err = clk_prepare_enable(pcie->pll);
+	if (err < 0)
+		return err;
+
+	err = reset_control_deassert(pcie->rst);
+	if (err < 0)
+		goto disable;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2);
+	value &= ~(XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_MASK <<
+		   XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_SHIFT);
+	value |= XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_VAL <<
+		 XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_SHIFT;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL2);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL5);
+	value &= ~(XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_MASK <<
+		   XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_SHIFT);
+	value |= XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_VAL <<
+		 XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_SHIFT;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL5);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+	value |= XUSB_PADCTL_UPHY_PLL_CTL1_PWR_OVRD;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2);
+	value |= XUSB_PADCTL_UPHY_PLL_CTL2_CAL_OVRD;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL2);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8);
+	value |= XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_OVRD;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL8);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL4);
+	value &= ~((XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_MASK <<
+		    XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT) |
+		   (XUSB_PADCTL_UPHY_PLL_CTL4_REFCLK_SEL_MASK <<
+		    XUSB_PADCTL_UPHY_PLL_CTL4_REFCLK_SEL_SHIFT));
+	value |= (XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_USB_VAL <<
+		  XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT) |
+		 XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_EN;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL4);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+	value &= ~((XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_MDIV_MASK <<
+		    XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_MDIV_SHIFT) |
+		   (XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_MASK <<
+		    XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SHIFT));
+	value |= XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_USB_VAL <<
+		 XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SHIFT;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+	value &= ~XUSB_PADCTL_UPHY_PLL_CTL1_IDDQ;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+	value &= ~(XUSB_PADCTL_UPHY_PLL_CTL1_SLEEP_MASK <<
+		   XUSB_PADCTL_UPHY_PLL_CTL1_SLEEP_SHIFT);
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+
+	usleep_range(10, 20);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL4);
+	value |= XUSB_PADCTL_UPHY_PLL_CTL4_REFCLKBUF_EN;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL4);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2);
+	value |= XUSB_PADCTL_UPHY_PLL_CTL2_CAL_EN;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL2);
+
+	timeout = jiffies + msecs_to_jiffies(100);
+
+	while (time_before(jiffies, timeout)) {
+		value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2);
+		if (value & XUSB_PADCTL_UPHY_PLL_CTL2_CAL_DONE)
+			break;
+
+		usleep_range(10, 20);
+	}
+
+	if (time_after_eq(jiffies, timeout)) {
+		err = -ETIMEDOUT;
+		goto reset;
+	}
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2);
+	value &= ~XUSB_PADCTL_UPHY_PLL_CTL2_CAL_EN;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL2);
+
+	timeout = jiffies + msecs_to_jiffies(100);
+
+	while (time_before(jiffies, timeout)) {
+		value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2);
+		if (!(value & XUSB_PADCTL_UPHY_PLL_CTL2_CAL_DONE))
+			break;
+
+		usleep_range(10, 20);
+	}
+
+	if (time_after_eq(jiffies, timeout)) {
+		err = -ETIMEDOUT;
+		goto reset;
+	}
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+	value |= XUSB_PADCTL_UPHY_PLL_CTL1_ENABLE;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+
+	timeout = jiffies + msecs_to_jiffies(100);
+
+	while (time_before(jiffies, timeout)) {
+		value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+		if (value & XUSB_PADCTL_UPHY_PLL_CTL1_LOCKDET_STATUS)
+			break;
+
+		usleep_range(10, 20);
+	}
+
+	if (time_after_eq(jiffies, timeout)) {
+		err = -ETIMEDOUT;
+		goto reset;
+	}
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8);
+	value |= XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_EN |
+		 XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_CLK_EN;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL8);
+
+	timeout = jiffies + msecs_to_jiffies(100);
+
+	while (time_before(jiffies, timeout)) {
+		value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8);
+		if (value & XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_DONE)
+			break;
+
+		usleep_range(10, 20);
+	}
+
+	if (time_after_eq(jiffies, timeout)) {
+		err = -ETIMEDOUT;
+		goto reset;
+	}
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8);
+	value &= ~XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_EN;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL8);
+
+	timeout = jiffies + msecs_to_jiffies(100);
+
+	while (time_before(jiffies, timeout)) {
+		value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8);
+		if (!(value & XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_DONE))
+			break;
+
+		usleep_range(10, 20);
+	}
+
+	if (time_after_eq(jiffies, timeout)) {
+		err = -ETIMEDOUT;
+		goto reset;
+	}
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8);
+	value &= ~XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_CLK_EN;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL8);
+
+	tegra210_xusb_pll_hw_control_enable();
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+	value &= ~XUSB_PADCTL_UPHY_PLL_CTL1_PWR_OVRD;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL1);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2);
+	value &= ~XUSB_PADCTL_UPHY_PLL_CTL2_CAL_OVRD;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL2);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8);
+	value &= ~XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_OVRD;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL8);
+
+	usleep_range(10, 20);
+
+	tegra210_xusb_pll_hw_sequence_start();
+
+	pcie->enable++;
+
+	return 0;
+
+reset:
+	reset_control_assert(pcie->rst);
+disable:
+	clk_disable_unprepare(pcie->pll);
+	return err;
+}
+
+static void tegra210_pex_uphy_disable(struct tegra_xusb_padctl *padctl)
+{
+	struct tegra_xusb_pcie_pad *pcie = to_pcie_pad(padctl->pcie);
+
+	mutex_lock(&padctl->lock);
+
+	if (WARN_ON(pcie->enable == 0))
+		goto unlock;
+
+	if (--pcie->enable > 0)
+		goto unlock;
+
+	reset_control_assert(pcie->rst);
+	clk_disable_unprepare(pcie->pll);
+
+unlock:
+	mutex_unlock(&padctl->lock);
+}
+
+/* must be called under padctl->lock */
+static int tegra210_sata_uphy_enable(struct tegra_xusb_padctl *padctl, bool usb)
+{
+	struct tegra_xusb_sata_pad *sata = to_sata_pad(padctl->sata);
+	unsigned long timeout;
+	u32 value;
+	int err;
+
+	if (sata->enable > 0) {
+		sata->enable++;
+		return 0;
+	}
+
+	err = clk_prepare_enable(sata->pll);
+	if (err < 0)
+		return err;
+
+	err = reset_control_deassert(sata->rst);
+	if (err < 0)
+		goto disable;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2);
+	value &= ~(XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_MASK <<
+		   XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_SHIFT);
+	value |= XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_VAL <<
+		 XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_SHIFT;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL2);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL5);
+	value &= ~(XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_MASK <<
+		   XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_SHIFT);
+	value |= XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_VAL <<
+		 XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_SHIFT;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL5);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+	value |= XUSB_PADCTL_UPHY_PLL_CTL1_PWR_OVRD;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2);
+	value |= XUSB_PADCTL_UPHY_PLL_CTL2_CAL_OVRD;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL2);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8);
+	value |= XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_OVRD;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL8);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL4);
+	value &= ~((XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_MASK <<
+		    XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT) |
+		   (XUSB_PADCTL_UPHY_PLL_CTL4_REFCLK_SEL_MASK <<
+		    XUSB_PADCTL_UPHY_PLL_CTL4_REFCLK_SEL_SHIFT));
+	value |= XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_EN;
+
+	if (usb)
+		value |= (XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_USB_VAL <<
+			  XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT);
+	else
+		value |= (XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SATA_VAL <<
+			  XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT);
+
+	/* XXX PLL0_XDIGCLK_EN */
+	/*
+	value &= ~(1 << 19);
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL4);
+	*/
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+	value &= ~((XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_MDIV_MASK <<
+		    XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_MDIV_SHIFT) |
+		   (XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_MASK <<
+		    XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SHIFT));
+
+	if (usb)
+		value |= XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_USB_VAL <<
+			 XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SHIFT;
+	else
+		value |= XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SATA_VAL <<
+			 XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SHIFT;
+
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+	value &= ~XUSB_PADCTL_UPHY_PLL_CTL1_IDDQ;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+	value &= ~(XUSB_PADCTL_UPHY_PLL_CTL1_SLEEP_MASK <<
+		   XUSB_PADCTL_UPHY_PLL_CTL1_SLEEP_SHIFT);
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+
+	usleep_range(10, 20);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL4);
+	value |= XUSB_PADCTL_UPHY_PLL_CTL4_REFCLKBUF_EN;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL4);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2);
+	value |= XUSB_PADCTL_UPHY_PLL_CTL2_CAL_EN;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL2);
+
+	timeout = jiffies + msecs_to_jiffies(100);
+
+	while (time_before(jiffies, timeout)) {
+		value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2);
+		if (value & XUSB_PADCTL_UPHY_PLL_CTL2_CAL_DONE)
+			break;
+
+		usleep_range(10, 20);
+	}
+
+	if (time_after_eq(jiffies, timeout)) {
+		err = -ETIMEDOUT;
+		goto reset;
+	}
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2);
+	value &= ~XUSB_PADCTL_UPHY_PLL_CTL2_CAL_EN;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL2);
+
+	timeout = jiffies + msecs_to_jiffies(100);
+
+	while (time_before(jiffies, timeout)) {
+		value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2);
+		if (!(value & XUSB_PADCTL_UPHY_PLL_CTL2_CAL_DONE))
+			break;
+
+		usleep_range(10, 20);
+	}
+
+	if (time_after_eq(jiffies, timeout)) {
+		err = -ETIMEDOUT;
+		goto reset;
+	}
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+	value |= XUSB_PADCTL_UPHY_PLL_CTL1_ENABLE;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+
+	timeout = jiffies + msecs_to_jiffies(100);
+
+	while (time_before(jiffies, timeout)) {
+		value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+		if (value & XUSB_PADCTL_UPHY_PLL_CTL1_LOCKDET_STATUS)
+			break;
+
+		usleep_range(10, 20);
+	}
+
+	if (time_after_eq(jiffies, timeout)) {
+		err = -ETIMEDOUT;
+		goto reset;
+	}
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8);
+	value |= XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_EN |
+		 XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_CLK_EN;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL8);
+
+	timeout = jiffies + msecs_to_jiffies(100);
+
+	while (time_before(jiffies, timeout)) {
+		value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8);
+		if (value & XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_DONE)
+			break;
+
+		usleep_range(10, 20);
+	}
+
+	if (time_after_eq(jiffies, timeout)) {
+		err = -ETIMEDOUT;
+		goto reset;
+	}
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8);
+	value &= ~XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_EN;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL8);
+
+	timeout = jiffies + msecs_to_jiffies(100);
+
+	while (time_before(jiffies, timeout)) {
+		value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8);
+		if (!(value & XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_DONE))
+			break;
+
+		usleep_range(10, 20);
+	}
+
+	if (time_after_eq(jiffies, timeout)) {
+		err = -ETIMEDOUT;
+		goto reset;
+	}
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8);
+	value &= ~XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_CLK_EN;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL8);
+
+	tegra210_sata_pll_hw_control_enable();
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+	value &= ~XUSB_PADCTL_UPHY_PLL_CTL1_PWR_OVRD;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL1);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2);
+	value &= ~XUSB_PADCTL_UPHY_PLL_CTL2_CAL_OVRD;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL2);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8);
+	value &= ~XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_OVRD;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL8);
+
+	usleep_range(10, 20);
+
+	tegra210_sata_pll_hw_sequence_start();
+
+	sata->enable++;
+
+	return 0;
+
+reset:
+	reset_control_assert(sata->rst);
+disable:
+	clk_disable_unprepare(sata->pll);
+	return err;
+}
+
+static void tegra210_sata_uphy_disable(struct tegra_xusb_padctl *padctl)
+{
+	struct tegra_xusb_sata_pad *sata = to_sata_pad(padctl->sata);
+
+	mutex_lock(&padctl->lock);
+
+	if (WARN_ON(sata->enable == 0))
+		goto unlock;
+
+	if (--sata->enable > 0)
+		goto unlock;
+
+	reset_control_assert(sata->rst);
+	clk_disable_unprepare(sata->pll);
+
+unlock:
+	mutex_unlock(&padctl->lock);
+}
+
+static int tegra210_xusb_padctl_enable(struct tegra_xusb_padctl *padctl)
+{
+	u32 value;
+
+	mutex_lock(&padctl->lock);
+
+	if (padctl->enable++ > 0)
+		goto out;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+	value &= ~XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN;
+	padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
+
+	usleep_range(100, 200);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+	value &= ~XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN_EARLY;
+	padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
+
+	usleep_range(100, 200);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+	value &= ~XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_VCORE_DOWN;
+	padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
+
+out:
+	mutex_unlock(&padctl->lock);
+	return 0;
+}
+
+static int tegra210_xusb_padctl_disable(struct tegra_xusb_padctl *padctl)
+{
+	u32 value;
+
+	mutex_lock(&padctl->lock);
+
+	if (WARN_ON(padctl->enable == 0))
+		goto out;
+
+	if (--padctl->enable > 0)
+		goto out;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+	value |= XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_VCORE_DOWN;
+	padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
+
+	usleep_range(100, 200);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+	value |= XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN_EARLY;
+	padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
+
+	usleep_range(100, 200);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+	value |= XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN;
+	padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
+
+out:
+	mutex_unlock(&padctl->lock);
+	return 0;
+}
+
+static int tegra210_hsic_set_idle(struct tegra_xusb_padctl *padctl,
+				  unsigned int index, bool idle)
+{
+	u32 value;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL0(index));
+
+	value &= ~(XUSB_PADCTL_HSIC_PAD_CTL0_RPU_DATA0 |
+		   XUSB_PADCTL_HSIC_PAD_CTL0_RPU_DATA1 |
+		   XUSB_PADCTL_HSIC_PAD_CTL0_RPD_STROBE);
+
+	if (idle)
+		value |= XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA0 |
+			 XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA1 |
+			 XUSB_PADCTL_HSIC_PAD_CTL0_RPU_STROBE;
+	else
+		value &= ~(XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA0 |
+			   XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA1 |
+			   XUSB_PADCTL_HSIC_PAD_CTL0_RPU_STROBE);
+
+	padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL0(index));
+
+	return 0;
+}
+
+static int tegra210_usb3_set_lfps_detect(struct tegra_xusb_padctl *padctl,
+					 unsigned int index, bool enable)
+{
+	struct tegra_xusb_port *port;
+	struct tegra_xusb_lane *lane;
+	u32 value, offset;
+
+	port = tegra_xusb_find_port(padctl, "usb3", index);
+	if (!port)
+		return -ENODEV;
+
+	lane = port->lane;
+
+	if (lane->pad == padctl->pcie)
+		offset = XUSB_PADCTL_UPHY_MISC_PAD_PX_CTL1(lane->index);
+	else
+		offset = XUSB_PADCTL_UPHY_MISC_PAD_S0_CTL1;
+
+	value = padctl_readl(padctl, offset);
+
+	value &= ~((XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_MASK <<
+		    XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_SHIFT) |
+		   XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_TERM_EN |
+		   XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_MODE_OVRD);
+
+	if (!enable) {
+		value |= (XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_VAL <<
+			  XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_SHIFT) |
+			 XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_TERM_EN |
+			 XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_MODE_OVRD;
+	}
+
+	padctl_writel(padctl, value, offset);
+
+	return 0;
+}
+
+#define TEGRA210_LANE(_name, _offset, _shift, _mask, _type)		\
+	{								\
+		.name = _name,						\
+		.offset = _offset,					\
+		.shift = _shift,					\
+		.mask = _mask,						\
+		.num_funcs = ARRAY_SIZE(tegra210_##_type##_functions),	\
+		.funcs = tegra210_##_type##_functions,			\
+	}
+
+static const char *tegra210_usb2_functions[] = {
+	"snps",
+	"xusb",
+	"uart"
+};
+
+static const struct tegra_xusb_lane_soc tegra210_usb2_lanes[] = {
+	TEGRA210_LANE("usb2-0", 0x004,  0, 0x3, usb2),
+	TEGRA210_LANE("usb2-1", 0x004,  2, 0x3, usb2),
+	TEGRA210_LANE("usb2-2", 0x004,  4, 0x3, usb2),
+	TEGRA210_LANE("usb2-3", 0x004,  6, 0x3, usb2),
+};
+
+static struct tegra_xusb_lane *
+tegra210_usb2_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np,
+			 unsigned int index)
+{
+	struct tegra_xusb_usb2_lane *usb2;
+	int err;
+
+	usb2 = kzalloc(sizeof(*usb2), GFP_KERNEL);
+	if (!usb2)
+		return ERR_PTR(-ENOMEM);
+
+	INIT_LIST_HEAD(&usb2->base.list);
+	usb2->base.soc = &pad->soc->lanes[index];
+	usb2->base.index = index;
+	usb2->base.pad = pad;
+	usb2->base.np = np;
+
+	err = tegra_xusb_lane_parse_dt(&usb2->base, np);
+	if (err < 0) {
+		kfree(usb2);
+		return ERR_PTR(err);
+	}
+
+	return &usb2->base;
+}
+
+static void tegra210_usb2_lane_remove(struct tegra_xusb_lane *lane)
+{
+	struct tegra_xusb_usb2_lane *usb2 = to_usb2_lane(lane);
+
+	kfree(usb2);
+}
+
+static const struct tegra_xusb_lane_ops tegra210_usb2_lane_ops = {
+	.probe = tegra210_usb2_lane_probe,
+	.remove = tegra210_usb2_lane_remove,
+};
+
+static int tegra210_usb2_phy_init(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	u32 value;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_USB2_PAD_MUX);
+	value &= ~(XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_MASK <<
+		   XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_SHIFT);
+	value |= XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_XUSB <<
+		 XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_SHIFT;
+	padctl_writel(padctl, value, XUSB_PADCTL_USB2_PAD_MUX);
+
+	return tegra210_xusb_padctl_enable(padctl);
+}
+
+static int tegra210_usb2_phy_exit(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+	return tegra210_xusb_padctl_disable(lane->pad->padctl);
+}
+
+static int tegra210_usb2_phy_power_on(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_usb2_lane *usb2 = to_usb2_lane(lane);
+	struct tegra_xusb_usb2_pad *pad = to_usb2_pad(lane->pad);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	struct tegra210_xusb_padctl *priv;
+	struct tegra_xusb_usb2_port *port;
+	unsigned int index = lane->index;
+	u32 value;
+	int err;
+
+	port = tegra_xusb_find_usb2_port(padctl, index);
+	if (!port) {
+		dev_err(&phy->dev, "no port found for USB2 lane %u\n", index);
+		return -ENODEV;
+	}
+
+	priv = to_tegra210_xusb_padctl(padctl);
+
+	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 |= (XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_VAL <<
+		  XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_SHIFT);
+
+	if (tegra_sku_info.revision < TEGRA_REVISION_A02)
+		value |=
+			(XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_VAL <<
+			XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_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_PORTX_CAP_MASK(index);
+	value |= XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_HOST(index);
+	padctl_writel(padctl, value, XUSB_PADCTL_USB2_PORT_CAP);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index));
+	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_PD |
+		   XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD2 |
+		   XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD_ZI);
+	value |= (priv->fuse.hs_curr_level[index] +
+		  usb2->hs_curr_level_offset) <<
+		 XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_SHIFT;
+	padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index));
+
+	value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
+	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_RPD_CTRL_MASK <<
+		    XUSB_PADCTL_USB2_OTG_PAD_CTL1_RPD_CTRL_SHIFT) |
+		   XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DR |
+		   XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_CHRP_OVRD |
+		   XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DISC_OVRD);
+	value |= (priv->fuse.hs_term_range_adj <<
+		  XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_SHIFT) |
+		 (priv->fuse.rpd_ctrl <<
+		  XUSB_PADCTL_USB2_OTG_PAD_CTL1_RPD_CTRL_SHIFT);
+	padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
+
+	value = padctl_readl(padctl,
+			     XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPADX_CTL1(index));
+	value &= ~(XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD_CTL1_VREG_LEV_MASK <<
+		   XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD_CTL1_VREG_LEV_SHIFT);
+	value |= XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD_CTL1_VREG_FIX18;
+	padctl_writel(padctl, value,
+		      XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPADX_CTL1(index));
+
+	err = regulator_enable(port->supply);
+	if (err)
+		return err;
+
+	mutex_lock(&padctl->lock);
+
+	if (pad->enable > 0) {
+		pad->enable++;
+		mutex_unlock(&padctl->lock);
+		return 0;
+	}
+
+	err = clk_prepare_enable(pad->clk);
+	if (err)
+		goto disable_regulator;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
+	value &= ~((XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_MASK <<
+		    XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_SHIFT) |
+		   (XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_MASK <<
+		    XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_SHIFT));
+	value |= (XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_VAL <<
+		  XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_SHIFT) |
+		 (XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_VAL <<
+		  XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_SHIFT);
+	padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
+
+	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);
+
+	udelay(1);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
+	value &= ~XUSB_PADCTL_USB2_BIAS_PAD_CTL1_PD_TRK;
+	padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
+
+	udelay(50);
+
+	clk_disable_unprepare(pad->clk);
+
+	pad->enable++;
+	mutex_unlock(&padctl->lock);
+
+	return 0;
+
+disable_regulator:
+	regulator_disable(port->supply);
+	mutex_unlock(&padctl->lock);
+	return err;
+}
+
+static int tegra210_usb2_phy_power_off(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_usb2_pad *pad = to_usb2_pad(lane->pad);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	struct tegra_xusb_usb2_port *port;
+	u32 value;
+
+	port = tegra_xusb_find_usb2_port(padctl, lane->index);
+	if (!port) {
+		dev_err(&phy->dev, "no port found for USB2 lane %u\n",
+			lane->index);
+		return -ENODEV;
+	}
+
+	mutex_lock(&padctl->lock);
+
+	if (WARN_ON(pad->enable == 0))
+		goto out;
+
+	if (--pad->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:
+	regulator_disable(port->supply);
+	mutex_unlock(&padctl->lock);
+	return 0;
+}
+
+static const struct phy_ops tegra210_usb2_phy_ops = {
+	.init = tegra210_usb2_phy_init,
+	.exit = tegra210_usb2_phy_exit,
+	.power_on = tegra210_usb2_phy_power_on,
+	.power_off = tegra210_usb2_phy_power_off,
+	.owner = THIS_MODULE,
+};
+
+static struct tegra_xusb_pad *
+tegra210_usb2_pad_probe(struct tegra_xusb_padctl *padctl,
+			const struct tegra_xusb_pad_soc *soc,
+			struct device_node *np)
+{
+	struct tegra_xusb_usb2_pad *usb2;
+	struct tegra_xusb_pad *pad;
+	int err;
+
+	usb2 = kzalloc(sizeof(*usb2), GFP_KERNEL);
+	if (!usb2)
+		return ERR_PTR(-ENOMEM);
+
+	pad = &usb2->base;
+	pad->ops = &tegra210_usb2_lane_ops;
+	pad->soc = soc;
+
+	err = tegra_xusb_pad_init(pad, padctl, np);
+	if (err < 0) {
+		kfree(usb2);
+		goto out;
+	}
+
+	usb2->clk = devm_clk_get(&pad->dev, "trk");
+	if (IS_ERR(usb2->clk)) {
+		err = PTR_ERR(usb2->clk);
+		dev_err(&pad->dev, "failed to get trk clock: %d\n", err);
+		goto unregister;
+	}
+
+	err = tegra_xusb_pad_register(pad, &tegra210_usb2_phy_ops);
+	if (err < 0)
+		goto unregister;
+
+	dev_set_drvdata(&pad->dev, pad);
+
+	return pad;
+
+unregister:
+	device_unregister(&pad->dev);
+out:
+	return ERR_PTR(err);
+}
+
+static void tegra210_usb2_pad_remove(struct tegra_xusb_pad *pad)
+{
+	struct tegra_xusb_usb2_pad *usb2 = to_usb2_pad(pad);
+
+	kfree(usb2);
+}
+
+static const struct tegra_xusb_pad_ops tegra210_usb2_ops = {
+	.probe = tegra210_usb2_pad_probe,
+	.remove = tegra210_usb2_pad_remove,
+};
+
+static const struct tegra_xusb_pad_soc tegra210_usb2_pad = {
+	.name = "usb2",
+	.num_lanes = ARRAY_SIZE(tegra210_usb2_lanes),
+	.lanes = tegra210_usb2_lanes,
+	.ops = &tegra210_usb2_ops,
+};
+
+static const char *tegra210_hsic_functions[] = {
+	"snps",
+	"xusb",
+};
+
+static const struct tegra_xusb_lane_soc tegra210_hsic_lanes[] = {
+	TEGRA210_LANE("hsic-0", 0x004, 14, 0x1, hsic),
+};
+
+static struct tegra_xusb_lane *
+tegra210_hsic_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np,
+			 unsigned int index)
+{
+	struct tegra_xusb_hsic_lane *hsic;
+	int err;
+
+	hsic = kzalloc(sizeof(*hsic), GFP_KERNEL);
+	if (!hsic)
+		return ERR_PTR(-ENOMEM);
+
+	INIT_LIST_HEAD(&hsic->base.list);
+	hsic->base.soc = &pad->soc->lanes[index];
+	hsic->base.index = index;
+	hsic->base.pad = pad;
+	hsic->base.np = np;
+
+	err = tegra_xusb_lane_parse_dt(&hsic->base, np);
+	if (err < 0) {
+		kfree(hsic);
+		return ERR_PTR(err);
+	}
+
+	return &hsic->base;
+}
+
+static void tegra210_hsic_lane_remove(struct tegra_xusb_lane *lane)
+{
+	struct tegra_xusb_hsic_lane *hsic = to_hsic_lane(lane);
+
+	kfree(hsic);
+}
+
+static const struct tegra_xusb_lane_ops tegra210_hsic_lane_ops = {
+	.probe = tegra210_hsic_lane_probe,
+	.remove = tegra210_hsic_lane_remove,
+};
+
+static int tegra210_hsic_phy_init(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	u32 value;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_USB2_PAD_MUX);
+	value &= ~(XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_MASK <<
+		   XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_SHIFT);
+	value |= XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_XUSB <<
+		 XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_SHIFT;
+	padctl_writel(padctl, value, XUSB_PADCTL_USB2_PAD_MUX);
+
+	return tegra210_xusb_padctl_enable(padctl);
+}
+
+static int tegra210_hsic_phy_exit(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+	return tegra210_xusb_padctl_disable(lane->pad->padctl);
+}
+
+static int tegra210_hsic_phy_power_on(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_hsic_lane *hsic = to_hsic_lane(lane);
+	struct tegra_xusb_hsic_pad *pad = to_hsic_pad(lane->pad);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	struct tegra210_xusb_padctl *priv;
+	unsigned int index = lane->index;
+	u32 value;
+	int err;
+
+	priv = to_tegra210_xusb_padctl(padctl);
+
+	err = regulator_enable(pad->supply);
+	if (err)
+		return err;
+
+	padctl_writel(padctl, hsic->strobe_trim,
+		      XUSB_PADCTL_HSIC_STRB_TRIM_CONTROL);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL1(index));
+	value &= ~(XUSB_PADCTL_HSIC_PAD_CTL1_TX_RTUNEP_MASK <<
+		   XUSB_PADCTL_HSIC_PAD_CTL1_TX_RTUNEP_SHIFT);
+	value |= (hsic->tx_rtune_p <<
+		  XUSB_PADCTL_HSIC_PAD_CTL1_TX_RTUNEP_SHIFT);
+	padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL1(index));
+
+	value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL2(index));
+	value &= ~((XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_MASK <<
+		    XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT) |
+		   (XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_MASK <<
+		    XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT));
+	value |= (hsic->rx_strobe_trim <<
+		  XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT) |
+		 (hsic->rx_data_trim <<
+		  XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT);
+	padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL2(index));
+
+	value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL0(index));
+	value &= ~(XUSB_PADCTL_HSIC_PAD_CTL0_RPU_DATA0 |
+		   XUSB_PADCTL_HSIC_PAD_CTL0_RPU_DATA1 |
+		   XUSB_PADCTL_HSIC_PAD_CTL0_RPU_STROBE |
+		   XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_DATA0 |
+		   XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_DATA1 |
+		   XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_STROBE |
+		   XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_DATA0 |
+		   XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_DATA1 |
+		   XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_STROBE |
+		   XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_DATA0 |
+		   XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_DATA1 |
+		   XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_STROBE);
+	value |= XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA0 |
+		 XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA1 |
+		 XUSB_PADCTL_HSIC_PAD_CTL0_RPD_STROBE;
+	padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL0(index));
+
+	err = clk_prepare_enable(pad->clk);
+	if (err)
+		goto disable;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PAD_TRK_CTL);
+	value &= ~((XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_MASK <<
+		    XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_SHIFT) |
+		   (XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_MASK <<
+		    XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_SHIFT));
+	value |= (XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_VAL <<
+		  XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_SHIFT) |
+		 (XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_VAL <<
+		  XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_SHIFT);
+	padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PAD_TRK_CTL);
+
+	udelay(1);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PAD_TRK_CTL);
+	value &= ~XUSB_PADCTL_HSIC_PAD_TRK_CTL_PD_TRK;
+	padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PAD_TRK_CTL);
+
+	udelay(50);
+
+	clk_disable_unprepare(pad->clk);
+
+	return 0;
+
+disable:
+	regulator_disable(pad->supply);
+	return err;
+}
+
+static int tegra210_hsic_phy_power_off(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_hsic_pad *pad = to_hsic_pad(lane->pad);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	unsigned int index = lane->index;
+	u32 value;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL0(index));
+	value |= XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_DATA0 |
+		 XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_DATA1 |
+		 XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_STROBE |
+		 XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_DATA0 |
+		 XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_DATA1 |
+		 XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_STROBE |
+		 XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_DATA0 |
+		 XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_DATA1 |
+		 XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_STROBE;
+	padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL1(index));
+
+	regulator_disable(pad->supply);
+
+	return 0;
+}
+
+static const struct phy_ops tegra210_hsic_phy_ops = {
+	.init = tegra210_hsic_phy_init,
+	.exit = tegra210_hsic_phy_exit,
+	.power_on = tegra210_hsic_phy_power_on,
+	.power_off = tegra210_hsic_phy_power_off,
+	.owner = THIS_MODULE,
+};
+
+static struct tegra_xusb_pad *
+tegra210_hsic_pad_probe(struct tegra_xusb_padctl *padctl,
+			const struct tegra_xusb_pad_soc *soc,
+			struct device_node *np)
+{
+	struct tegra_xusb_hsic_pad *hsic;
+	struct tegra_xusb_pad *pad;
+	int err;
+
+	hsic = kzalloc(sizeof(*hsic), GFP_KERNEL);
+	if (!hsic)
+		return ERR_PTR(-ENOMEM);
+
+	pad = &hsic->base;
+	pad->ops = &tegra210_hsic_lane_ops;
+	pad->soc = soc;
+
+	err = tegra_xusb_pad_init(pad, padctl, np);
+	if (err < 0) {
+		kfree(hsic);
+		goto out;
+	}
+
+	hsic->clk = devm_clk_get(&pad->dev, "trk");
+	if (IS_ERR(hsic->clk)) {
+		err = PTR_ERR(hsic->clk);
+		dev_err(&pad->dev, "failed to get trk clock: %d\n", err);
+		goto unregister;
+	}
+
+	err = tegra_xusb_pad_register(pad, &tegra210_hsic_phy_ops);
+	if (err < 0)
+		goto unregister;
+
+	dev_set_drvdata(&pad->dev, pad);
+
+	return pad;
+
+unregister:
+	device_unregister(&pad->dev);
+out:
+	return ERR_PTR(err);
+}
+
+static void tegra210_hsic_pad_remove(struct tegra_xusb_pad *pad)
+{
+	struct tegra_xusb_hsic_pad *hsic = to_hsic_pad(pad);
+
+	kfree(hsic);
+}
+
+static const struct tegra_xusb_pad_ops tegra210_hsic_ops = {
+	.probe = tegra210_hsic_pad_probe,
+	.remove = tegra210_hsic_pad_remove,
+};
+
+static const struct tegra_xusb_pad_soc tegra210_hsic_pad = {
+	.name = "hsic",
+	.num_lanes = ARRAY_SIZE(tegra210_hsic_lanes),
+	.lanes = tegra210_hsic_lanes,
+	.ops = &tegra210_hsic_ops,
+};
+
+static const char *tegra210_pcie_functions[] = {
+	"pcie-x1",
+	"usb3-ss",
+	"sata",
+	"pcie-x4",
+};
+
+static const struct tegra_xusb_lane_soc tegra210_pcie_lanes[] = {
+	TEGRA210_LANE("pcie-0", 0x028, 12, 0x3, pcie),
+	TEGRA210_LANE("pcie-1", 0x028, 14, 0x3, pcie),
+	TEGRA210_LANE("pcie-2", 0x028, 16, 0x3, pcie),
+	TEGRA210_LANE("pcie-3", 0x028, 18, 0x3, pcie),
+	TEGRA210_LANE("pcie-4", 0x028, 20, 0x3, pcie),
+	TEGRA210_LANE("pcie-5", 0x028, 22, 0x3, pcie),
+	TEGRA210_LANE("pcie-6", 0x028, 24, 0x3, pcie),
+};
+
+static struct tegra_xusb_lane *
+tegra210_pcie_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np,
+			 unsigned int index)
+{
+	struct tegra_xusb_pcie_lane *pcie;
+	int err;
+
+	pcie = kzalloc(sizeof(*pcie), GFP_KERNEL);
+	if (!pcie)
+		return ERR_PTR(-ENOMEM);
+
+	INIT_LIST_HEAD(&pcie->base.list);
+	pcie->base.soc = &pad->soc->lanes[index];
+	pcie->base.index = index;
+	pcie->base.pad = pad;
+	pcie->base.np = np;
+
+	err = tegra_xusb_lane_parse_dt(&pcie->base, np);
+	if (err < 0) {
+		kfree(pcie);
+		return ERR_PTR(err);
+	}
+
+	return &pcie->base;
+}
+
+static void tegra210_pcie_lane_remove(struct tegra_xusb_lane *lane)
+{
+	struct tegra_xusb_pcie_lane *pcie = to_pcie_lane(lane);
+
+	kfree(pcie);
+}
+
+static const struct tegra_xusb_lane_ops tegra210_pcie_lane_ops = {
+	.probe = tegra210_pcie_lane_probe,
+	.remove = tegra210_pcie_lane_remove,
+};
+
+static int tegra210_pcie_phy_init(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+	return tegra210_xusb_padctl_enable(lane->pad->padctl);
+}
+
+static int tegra210_pcie_phy_exit(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+	return tegra210_xusb_padctl_disable(lane->pad->padctl);
+}
+
+static int tegra210_pcie_phy_power_on(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	u32 value;
+	int err;
+
+	mutex_lock(&padctl->lock);
+
+	err = tegra210_pex_uphy_enable(padctl);
+	if (err < 0)
+		goto unlock;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX);
+	value |= XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(lane->index);
+	padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX);
+
+unlock:
+	mutex_unlock(&padctl->lock);
+	return err;
+}
+
+static int tegra210_pcie_phy_power_off(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	u32 value;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX);
+	value &= ~XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(lane->index);
+	padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX);
+
+	tegra210_pex_uphy_disable(padctl);
+
+	return 0;
+}
+
+static const struct phy_ops tegra210_pcie_phy_ops = {
+	.init = tegra210_pcie_phy_init,
+	.exit = tegra210_pcie_phy_exit,
+	.power_on = tegra210_pcie_phy_power_on,
+	.power_off = tegra210_pcie_phy_power_off,
+	.owner = THIS_MODULE,
+};
+
+static struct tegra_xusb_pad *
+tegra210_pcie_pad_probe(struct tegra_xusb_padctl *padctl,
+			const struct tegra_xusb_pad_soc *soc,
+			struct device_node *np)
+{
+	struct tegra_xusb_pcie_pad *pcie;
+	struct tegra_xusb_pad *pad;
+	int err;
+
+	pcie = kzalloc(sizeof(*pcie), GFP_KERNEL);
+	if (!pcie)
+		return ERR_PTR(-ENOMEM);
+
+	pad = &pcie->base;
+	pad->ops = &tegra210_pcie_lane_ops;
+	pad->soc = soc;
+
+	err = tegra_xusb_pad_init(pad, padctl, np);
+	if (err < 0) {
+		kfree(pcie);
+		goto out;
+	}
+
+	pcie->pll = devm_clk_get(&pad->dev, "pll");
+	if (IS_ERR(pcie->pll)) {
+		err = PTR_ERR(pcie->pll);
+		dev_err(&pad->dev, "failed to get PLL: %d\n", err);
+		goto unregister;
+	}
+
+	pcie->rst = devm_reset_control_get(&pad->dev, "phy");
+	if (IS_ERR(pcie->rst)) {
+		err = PTR_ERR(pcie->rst);
+		dev_err(&pad->dev, "failed to get PCIe pad reset: %d\n", err);
+		goto unregister;
+	}
+
+	err = tegra_xusb_pad_register(pad, &tegra210_pcie_phy_ops);
+	if (err < 0)
+		goto unregister;
+
+	dev_set_drvdata(&pad->dev, pad);
+
+	return pad;
+
+unregister:
+	device_unregister(&pad->dev);
+out:
+	return ERR_PTR(err);
+}
+
+static void tegra210_pcie_pad_remove(struct tegra_xusb_pad *pad)
+{
+	struct tegra_xusb_pcie_pad *pcie = to_pcie_pad(pad);
+
+	kfree(pcie);
+}
+
+static const struct tegra_xusb_pad_ops tegra210_pcie_ops = {
+	.probe = tegra210_pcie_pad_probe,
+	.remove = tegra210_pcie_pad_remove,
+};
+
+static const struct tegra_xusb_pad_soc tegra210_pcie_pad = {
+	.name = "pcie",
+	.num_lanes = ARRAY_SIZE(tegra210_pcie_lanes),
+	.lanes = tegra210_pcie_lanes,
+	.ops = &tegra210_pcie_ops,
+};
+
+static const struct tegra_xusb_lane_soc tegra210_sata_lanes[] = {
+	TEGRA210_LANE("sata-0", 0x028, 30, 0x3, pcie),
+};
+
+static struct tegra_xusb_lane *
+tegra210_sata_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np,
+			 unsigned int index)
+{
+	struct tegra_xusb_sata_lane *sata;
+	int err;
+
+	sata = kzalloc(sizeof(*sata), GFP_KERNEL);
+	if (!sata)
+		return ERR_PTR(-ENOMEM);
+
+	INIT_LIST_HEAD(&sata->base.list);
+	sata->base.soc = &pad->soc->lanes[index];
+	sata->base.index = index;
+	sata->base.pad = pad;
+	sata->base.np = np;
+
+	err = tegra_xusb_lane_parse_dt(&sata->base, np);
+	if (err < 0) {
+		kfree(sata);
+		return ERR_PTR(err);
+	}
+
+	return &sata->base;
+}
+
+static void tegra210_sata_lane_remove(struct tegra_xusb_lane *lane)
+{
+	struct tegra_xusb_sata_lane *sata = to_sata_lane(lane);
+
+	kfree(sata);
+}
+
+static const struct tegra_xusb_lane_ops tegra210_sata_lane_ops = {
+	.probe = tegra210_sata_lane_probe,
+	.remove = tegra210_sata_lane_remove,
+};
+
+static int tegra210_sata_phy_init(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+	return tegra210_xusb_padctl_enable(lane->pad->padctl);
+}
+
+static int tegra210_sata_phy_exit(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+	return tegra210_xusb_padctl_disable(lane->pad->padctl);
+}
+
+static int tegra210_sata_phy_power_on(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	u32 value;
+	int err;
+
+	mutex_lock(&padctl->lock);
+
+	err = tegra210_sata_uphy_enable(padctl, false);
+	if (err < 0)
+		goto unlock;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX);
+	value |= XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(lane->index);
+	padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX);
+
+unlock:
+	mutex_unlock(&padctl->lock);
+	return err;
+}
+
+static int tegra210_sata_phy_power_off(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	u32 value;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX);
+	value &= ~XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(lane->index);
+	padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX);
+
+	tegra210_sata_uphy_disable(lane->pad->padctl);
+
+	return 0;
+}
+
+static const struct phy_ops tegra210_sata_phy_ops = {
+	.init = tegra210_sata_phy_init,
+	.exit = tegra210_sata_phy_exit,
+	.power_on = tegra210_sata_phy_power_on,
+	.power_off = tegra210_sata_phy_power_off,
+	.owner = THIS_MODULE,
+};
+
+static struct tegra_xusb_pad *
+tegra210_sata_pad_probe(struct tegra_xusb_padctl *padctl,
+			const struct tegra_xusb_pad_soc *soc,
+			struct device_node *np)
+{
+	struct tegra_xusb_sata_pad *sata;
+	struct tegra_xusb_pad *pad;
+	int err;
+
+	sata = kzalloc(sizeof(*sata), GFP_KERNEL);
+	if (!sata)
+		return ERR_PTR(-ENOMEM);
+
+	pad = &sata->base;
+	pad->ops = &tegra210_sata_lane_ops;
+	pad->soc = soc;
+
+	err = tegra_xusb_pad_init(pad, padctl, np);
+	if (err < 0) {
+		kfree(sata);
+		goto out;
+	}
+
+	sata->rst = devm_reset_control_get(&pad->dev, "phy");
+	if (IS_ERR(sata->rst)) {
+		err = PTR_ERR(sata->rst);
+		dev_err(&pad->dev, "failed to get SATA pad reset: %d\n", err);
+		goto unregister;
+	}
+
+	err = tegra_xusb_pad_register(pad, &tegra210_sata_phy_ops);
+	if (err < 0)
+		goto unregister;
+
+	dev_set_drvdata(&pad->dev, pad);
+
+	return pad;
+
+unregister:
+	device_unregister(&pad->dev);
+out:
+	return ERR_PTR(err);
+}
+
+static void tegra210_sata_pad_remove(struct tegra_xusb_pad *pad)
+{
+	struct tegra_xusb_sata_pad *sata = to_sata_pad(pad);
+
+	kfree(sata);
+}
+
+static const struct tegra_xusb_pad_ops tegra210_sata_ops = {
+	.probe = tegra210_sata_pad_probe,
+	.remove = tegra210_sata_pad_remove,
+};
+
+static const struct tegra_xusb_pad_soc tegra210_sata_pad = {
+	.name = "sata",
+	.num_lanes = ARRAY_SIZE(tegra210_sata_lanes),
+	.lanes = tegra210_sata_lanes,
+	.ops = &tegra210_sata_ops,
+};
+
+static const struct tegra_xusb_pad_soc * const tegra210_pads[] = {
+	&tegra210_usb2_pad,
+	&tegra210_hsic_pad,
+	&tegra210_pcie_pad,
+	&tegra210_sata_pad,
+};
+
+static int tegra210_usb2_port_enable(struct tegra_xusb_port *port)
+{
+	return 0;
+}
+
+static void tegra210_usb2_port_disable(struct tegra_xusb_port *port)
+{
+}
+
+static struct tegra_xusb_lane *
+tegra210_usb2_port_map(struct tegra_xusb_port *port)
+{
+	return tegra_xusb_find_lane(port->padctl, "usb2", port->index);
+}
+
+static const struct tegra_xusb_port_ops tegra210_usb2_port_ops = {
+	.enable = tegra210_usb2_port_enable,
+	.disable = tegra210_usb2_port_disable,
+	.map = tegra210_usb2_port_map,
+};
+
+static int tegra210_hsic_port_enable(struct tegra_xusb_port *port)
+{
+	return 0;
+}
+
+static void tegra210_hsic_port_disable(struct tegra_xusb_port *port)
+{
+}
+
+static struct tegra_xusb_lane *
+tegra210_hsic_port_map(struct tegra_xusb_port *port)
+{
+	return tegra_xusb_find_lane(port->padctl, "hsic", port->index);
+}
+
+static const struct tegra_xusb_port_ops tegra210_hsic_port_ops = {
+	.enable = tegra210_hsic_port_enable,
+	.disable = tegra210_hsic_port_disable,
+	.map = tegra210_hsic_port_map,
+};
+
+static int tegra210_usb3_port_enable(struct tegra_xusb_port *port)
+{
+	struct tegra_xusb_usb3_port *usb3 = to_usb3_port(port);
+	struct tegra_xusb_padctl *padctl = port->padctl;
+	struct tegra_xusb_lane *lane = usb3->base.lane;
+	unsigned int index = port->index;
+	u32 value;
+	int err;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_MAP);
+
+	if (!usb3->internal)
+		value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(index);
+	else
+		value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(index);
+
+	value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(index);
+	value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(index, usb3->port);
+	padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_MAP);
+
+	/*
+	 * TODO: move this code into the PCIe/SATA PHY ->power_on() callbacks
+	 * and conditionalize based on mux function? This seems to work, but
+	 * might not be the exact proper sequence.
+	 */
+	err = regulator_enable(usb3->supply);
+	if (err < 0)
+		return err;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_USB3_PADX_ECTL1(index));
+	value &= ~(XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_MASK <<
+		   XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_SHIFT);
+	value |= XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_VAL <<
+		 XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_SHIFT;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_USB3_PADX_ECTL1(index));
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_USB3_PADX_ECTL2(index));
+	value &= ~(XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_MASK <<
+		   XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_SHIFT);
+	value |= XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_VAL <<
+		 XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_SHIFT;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_USB3_PADX_ECTL2(index));
+
+	padctl_writel(padctl, XUSB_PADCTL_UPHY_USB3_PAD_ECTL3_RX_DFE_VAL,
+		      XUSB_PADCTL_UPHY_USB3_PADX_ECTL3(index));
+
+	value = padctl_readl(padctl, XUSB_PADCTL_UPHY_USB3_PADX_ECTL4(index));
+	value &= ~(XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_MASK <<
+		   XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_SHIFT);
+	value |= XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_VAL <<
+		 XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_SHIFT;
+	padctl_writel(padctl, value, XUSB_PADCTL_UPHY_USB3_PADX_ECTL4(index));
+
+	padctl_writel(padctl, XUSB_PADCTL_UPHY_USB3_PAD_ECTL6_RX_EQ_CTRL_H_VAL,
+		      XUSB_PADCTL_UPHY_USB3_PADX_ECTL6(index));
+
+	if (lane->pad == padctl->sata)
+		err = tegra210_sata_uphy_enable(padctl, true);
+	else
+		err = tegra210_pex_uphy_enable(padctl);
+
+	if (err) {
+		dev_err(&port->dev, "%s: failed to enable UPHY: %d\n",
+			__func__, err);
+		return err;
+	}
+
+	value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+	value &= ~XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_VCORE_DOWN(index);
+	padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
+
+	usleep_range(100, 200);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+	value &= ~XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN_EARLY(index);
+	padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
+
+	usleep_range(100, 200);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+	value &= ~XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN(index);
+	padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
+
+	return 0;
+}
+
+static void tegra210_usb3_port_disable(struct tegra_xusb_port *port)
+{
+	struct tegra_xusb_usb3_port *usb3 = to_usb3_port(port);
+	struct tegra_xusb_padctl *padctl = port->padctl;
+	struct tegra_xusb_lane *lane = port->lane;
+	unsigned int index = port->index;
+	u32 value;
+
+	value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+	value |= XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN_EARLY(index);
+	padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
+
+	usleep_range(100, 200);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+	value |= XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN(index);
+	padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
+
+	usleep_range(250, 350);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+	value |= XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_VCORE_DOWN(index);
+	padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1);
+
+	if (lane->pad == padctl->sata)
+		tegra210_sata_uphy_disable(padctl);
+	else
+		tegra210_pex_uphy_disable(padctl);
+
+	regulator_disable(usb3->supply);
+
+	value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_MAP);
+	value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(index);
+	value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(index, 0x7);
+	padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_MAP);
+}
+
+static const struct tegra_xusb_lane_map tegra210_usb3_map[] = {
+	{ 0, "pcie", 6 },
+	{ 1, "pcie", 5 },
+	{ 2, "pcie", 0 },
+	{ 2, "pcie", 3 },
+	{ 3, "pcie", 4 },
+	{ 3, "pcie", 4 },
+	{ 0, NULL,   0 }
+};
+
+static struct tegra_xusb_lane *
+tegra210_usb3_port_map(struct tegra_xusb_port *port)
+{
+	return tegra_xusb_port_find_lane(port, tegra210_usb3_map, "usb3-ss");
+}
+
+static const struct tegra_xusb_port_ops tegra210_usb3_port_ops = {
+	.enable = tegra210_usb3_port_enable,
+	.disable = tegra210_usb3_port_disable,
+	.map = tegra210_usb3_port_map,
+};
+
+static int
+tegra210_xusb_read_fuse_calibration(struct tegra210_xusb_fuse_calibration *fuse)
+{
+	unsigned int i;
+	u32 value;
+	int err;
+
+	err = tegra_fuse_readl(TEGRA_FUSE_SKU_CALIB_0, &value);
+	if (err < 0)
+		return err;
+
+	for (i = 0; i < ARRAY_SIZE(fuse->hs_curr_level); i++) {
+		fuse->hs_curr_level[i] =
+			(value >> FUSE_SKU_CALIB_HS_CURR_LEVEL_PADX_SHIFT(i)) &
+			FUSE_SKU_CALIB_HS_CURR_LEVEL_PAD_MASK;
+	}
+
+	fuse->hs_term_range_adj =
+		(value >> FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_SHIFT) &
+		FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_MASK;
+
+	err = tegra_fuse_readl(TEGRA_FUSE_USB_CALIB_EXT_0, &value);
+	if (err < 0)
+		return err;
+
+	fuse->rpd_ctrl =
+		(value >> FUSE_USB_CALIB_EXT_RPD_CTRL_SHIFT) &
+		FUSE_USB_CALIB_EXT_RPD_CTRL_MASK;
+
+	return 0;
+}
+
+static struct tegra_xusb_padctl *
+tegra210_xusb_padctl_probe(struct device *dev,
+			   const struct tegra_xusb_padctl_soc *soc)
+{
+	struct tegra210_xusb_padctl *padctl;
+	int err;
+
+	padctl = devm_kzalloc(dev, sizeof(*padctl), GFP_KERNEL);
+	if (!padctl)
+		return ERR_PTR(-ENOMEM);
+
+	padctl->base.dev = dev;
+	padctl->base.soc = soc;
+
+	err = tegra210_xusb_read_fuse_calibration(&padctl->fuse);
+	if (err < 0)
+		return ERR_PTR(err);
+
+	return &padctl->base;
+}
+
+static void tegra210_xusb_padctl_remove(struct tegra_xusb_padctl *padctl)
+{
+}
+
+static const struct tegra_xusb_padctl_ops tegra210_xusb_padctl_ops = {
+	.probe = tegra210_xusb_padctl_probe,
+	.remove = tegra210_xusb_padctl_remove,
+	.usb3_set_lfps_detect = tegra210_usb3_set_lfps_detect,
+	.hsic_set_idle = tegra210_hsic_set_idle,
+};
+
+const struct tegra_xusb_padctl_soc tegra210_xusb_padctl_soc = {
+	.num_pads = ARRAY_SIZE(tegra210_pads),
+	.pads = tegra210_pads,
+	.ports = {
+		.usb2 = {
+			.ops = &tegra210_usb2_port_ops,
+			.count = 4,
+		},
+		.hsic = {
+			.ops = &tegra210_hsic_port_ops,
+			.count = 1,
+		},
+		.usb3 = {
+			.ops = &tegra210_usb3_port_ops,
+			.count = 4,
+		},
+	},
+	.ops = &tegra210_xusb_padctl_ops,
+};
+EXPORT_SYMBOL_GPL(tegra210_xusb_padctl_soc);
+
+MODULE_AUTHOR("Andrew Bresticker <abrestic@chromium.org>");
+MODULE_DESCRIPTION("NVIDIA Tegra 210 XUSB Pad Controller driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c
new file mode 100644
index 0000000..ec83dfd
--- /dev/null
+++ b/drivers/phy/tegra/xusb.c
@@ -0,0 +1,1021 @@
+/*
+ * Copyright (c) 2014-2015, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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/delay.h>
+#include <linux/io.h>
+#include <linux/mailbox_client.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include <soc/tegra/fuse.h>
+
+#include "xusb.h"
+
+static struct phy *tegra_xusb_pad_of_xlate(struct device *dev,
+					   struct of_phandle_args *args)
+{
+	struct tegra_xusb_pad *pad = dev_get_drvdata(dev);
+	struct phy *phy = NULL;
+	unsigned int i;
+
+	if (args->args_count != 0)
+		return ERR_PTR(-EINVAL);
+
+	for (i = 0; i < pad->soc->num_lanes; i++) {
+		if (!pad->lanes[i])
+			continue;
+
+		if (pad->lanes[i]->dev.of_node == args->np) {
+			phy = pad->lanes[i];
+			break;
+		}
+	}
+
+	if (phy == NULL)
+		phy = ERR_PTR(-ENODEV);
+
+	return phy;
+}
+
+static const struct of_device_id tegra_xusb_padctl_of_match[] = {
+#if defined(CONFIG_ARCH_TEGRA_124_SOC) || defined(CONFIG_ARCH_TEGRA_132_SOC)
+	{
+		.compatible = "nvidia,tegra124-xusb-padctl",
+		.data = &tegra124_xusb_padctl_soc,
+	},
+#endif
+#if defined(CONFIG_ARCH_TEGRA_210_SOC)
+	{
+		.compatible = "nvidia,tegra210-xusb-padctl",
+		.data = &tegra210_xusb_padctl_soc,
+	},
+#endif
+	{ }
+};
+MODULE_DEVICE_TABLE(of, tegra_xusb_padctl_of_match);
+
+static struct device_node *
+tegra_xusb_find_pad_node(struct tegra_xusb_padctl *padctl, const char *name)
+{
+	/*
+	 * of_find_node_by_name() drops a reference, so make sure to grab one.
+	 */
+	struct device_node *np = of_node_get(padctl->dev->of_node);
+
+	np = of_find_node_by_name(np, "pads");
+	if (np)
+		np = of_find_node_by_name(np, name);
+
+	return np;
+}
+
+static struct device_node *
+tegra_xusb_pad_find_phy_node(struct tegra_xusb_pad *pad, unsigned int index)
+{
+	/*
+	 * of_find_node_by_name() drops a reference, so make sure to grab one.
+	 */
+	struct device_node *np = of_node_get(pad->dev.of_node);
+
+	np = of_find_node_by_name(np, "lanes");
+	if (!np)
+		return NULL;
+
+	return of_find_node_by_name(np, pad->soc->lanes[index].name);
+}
+
+int tegra_xusb_lane_lookup_function(struct tegra_xusb_lane *lane,
+				    const char *function)
+{
+	unsigned int i;
+
+	for (i = 0; i < lane->soc->num_funcs; i++)
+		if (strcmp(function, lane->soc->funcs[i]) == 0)
+			return i;
+
+	return -EINVAL;
+}
+
+int tegra_xusb_lane_parse_dt(struct tegra_xusb_lane *lane,
+			     struct device_node *np)
+{
+	struct device *dev = &lane->pad->dev;
+	const char *function;
+	int err;
+
+	err = of_property_read_string(np, "nvidia,function", &function);
+	if (err < 0)
+		return err;
+
+	err = tegra_xusb_lane_lookup_function(lane, function);
+	if (err < 0) {
+		dev_err(dev, "invalid function \"%s\" for lane \"%s\"\n",
+			function, np->name);
+		return err;
+	}
+
+	lane->function = err;
+
+	return 0;
+}
+
+static void tegra_xusb_lane_destroy(struct phy *phy)
+{
+	if (phy) {
+		struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+
+		lane->pad->ops->remove(lane);
+		phy_destroy(phy);
+	}
+}
+
+static void tegra_xusb_pad_release(struct device *dev)
+{
+	struct tegra_xusb_pad *pad = to_tegra_xusb_pad(dev);
+
+	pad->soc->ops->remove(pad);
+}
+
+static struct device_type tegra_xusb_pad_type = {
+	.release = tegra_xusb_pad_release,
+};
+
+int tegra_xusb_pad_init(struct tegra_xusb_pad *pad,
+			struct tegra_xusb_padctl *padctl,
+			struct device_node *np)
+{
+	int err;
+
+	device_initialize(&pad->dev);
+	INIT_LIST_HEAD(&pad->list);
+	pad->dev.parent = padctl->dev;
+	pad->dev.type = &tegra_xusb_pad_type;
+	pad->dev.of_node = np;
+	pad->padctl = padctl;
+
+	err = dev_set_name(&pad->dev, "%s", pad->soc->name);
+	if (err < 0)
+		goto unregister;
+
+	err = device_add(&pad->dev);
+	if (err < 0)
+		goto unregister;
+
+	return 0;
+
+unregister:
+	device_unregister(&pad->dev);
+	return err;
+}
+
+int tegra_xusb_pad_register(struct tegra_xusb_pad *pad,
+			    const struct phy_ops *ops)
+{
+	struct device_node *children;
+	struct phy *lane;
+	unsigned int i;
+	int err;
+
+	children = of_find_node_by_name(pad->dev.of_node, "lanes");
+	if (!children)
+		return -ENODEV;
+
+	pad->lanes = devm_kcalloc(&pad->dev, pad->soc->num_lanes, sizeof(lane),
+				  GFP_KERNEL);
+	if (!pad->lanes) {
+		of_node_put(children);
+		return -ENOMEM;
+	}
+
+	for (i = 0; i < pad->soc->num_lanes; i++) {
+		struct device_node *np = tegra_xusb_pad_find_phy_node(pad, i);
+		struct tegra_xusb_lane *lane;
+
+		/* skip disabled lanes */
+		if (!np || !of_device_is_available(np)) {
+			of_node_put(np);
+			continue;
+		}
+
+		pad->lanes[i] = phy_create(&pad->dev, np, ops);
+		if (IS_ERR(pad->lanes[i])) {
+			err = PTR_ERR(pad->lanes[i]);
+			of_node_put(np);
+			goto remove;
+		}
+
+		lane = pad->ops->probe(pad, np, i);
+		if (IS_ERR(lane)) {
+			phy_destroy(pad->lanes[i]);
+			err = PTR_ERR(lane);
+			goto remove;
+		}
+
+		list_add_tail(&lane->list, &pad->padctl->lanes);
+		phy_set_drvdata(pad->lanes[i], lane);
+	}
+
+	pad->provider = of_phy_provider_register_full(&pad->dev, children,
+						      tegra_xusb_pad_of_xlate);
+	if (IS_ERR(pad->provider)) {
+		err = PTR_ERR(pad->provider);
+		goto remove;
+	}
+
+	return 0;
+
+remove:
+	while (i--)
+		tegra_xusb_lane_destroy(pad->lanes[i]);
+
+	of_node_put(children);
+
+	return err;
+}
+
+void tegra_xusb_pad_unregister(struct tegra_xusb_pad *pad)
+{
+	unsigned int i = pad->soc->num_lanes;
+
+	of_phy_provider_unregister(pad->provider);
+
+	while (i--)
+		tegra_xusb_lane_destroy(pad->lanes[i]);
+
+	device_unregister(&pad->dev);
+}
+
+static struct tegra_xusb_pad *
+tegra_xusb_pad_create(struct tegra_xusb_padctl *padctl,
+		      const struct tegra_xusb_pad_soc *soc)
+{
+	struct tegra_xusb_pad *pad;
+	struct device_node *np;
+	int err;
+
+	np = tegra_xusb_find_pad_node(padctl, soc->name);
+	if (!np || !of_device_is_available(np))
+		return NULL;
+
+	pad = soc->ops->probe(padctl, soc, np);
+	if (IS_ERR(pad)) {
+		err = PTR_ERR(pad);
+		dev_err(padctl->dev, "failed to create pad %s: %d\n",
+			soc->name, err);
+		return ERR_PTR(err);
+	}
+
+	/* XXX move this into ->probe() to avoid string comparison */
+	if (strcmp(soc->name, "pcie") == 0)
+		padctl->pcie = pad;
+
+	if (strcmp(soc->name, "sata") == 0)
+		padctl->sata = pad;
+
+	if (strcmp(soc->name, "usb2") == 0)
+		padctl->usb2 = pad;
+
+	if (strcmp(soc->name, "ulpi") == 0)
+		padctl->ulpi = pad;
+
+	if (strcmp(soc->name, "hsic") == 0)
+		padctl->hsic = pad;
+
+	return pad;
+}
+
+static void __tegra_xusb_remove_pads(struct tegra_xusb_padctl *padctl)
+{
+	struct tegra_xusb_pad *pad, *tmp;
+
+	list_for_each_entry_safe_reverse(pad, tmp, &padctl->pads, list) {
+		list_del(&pad->list);
+		tegra_xusb_pad_unregister(pad);
+	}
+}
+
+static void tegra_xusb_remove_pads(struct tegra_xusb_padctl *padctl)
+{
+	mutex_lock(&padctl->lock);
+	__tegra_xusb_remove_pads(padctl);
+	mutex_unlock(&padctl->lock);
+}
+
+static void tegra_xusb_lane_program(struct tegra_xusb_lane *lane)
+{
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	const struct tegra_xusb_lane_soc *soc = lane->soc;
+	u32 value;
+
+	/* choose function */
+	value = padctl_readl(padctl, soc->offset);
+	value &= ~(soc->mask << soc->shift);
+	value |= lane->function << soc->shift;
+	padctl_writel(padctl, value, soc->offset);
+}
+
+static void tegra_xusb_pad_program(struct tegra_xusb_pad *pad)
+{
+	unsigned int i;
+
+	for (i = 0; i < pad->soc->num_lanes; i++) {
+		struct tegra_xusb_lane *lane;
+
+		if (pad->lanes[i]) {
+			lane = phy_get_drvdata(pad->lanes[i]);
+			tegra_xusb_lane_program(lane);
+		}
+	}
+}
+
+static int tegra_xusb_setup_pads(struct tegra_xusb_padctl *padctl)
+{
+	struct tegra_xusb_pad *pad;
+	unsigned int i;
+
+	mutex_lock(&padctl->lock);
+
+	for (i = 0; i < padctl->soc->num_pads; i++) {
+		const struct tegra_xusb_pad_soc *soc = padctl->soc->pads[i];
+		int err;
+
+		pad = tegra_xusb_pad_create(padctl, soc);
+		if (IS_ERR(pad)) {
+			err = PTR_ERR(pad);
+			dev_err(padctl->dev, "failed to create pad %s: %d\n",
+				soc->name, err);
+			__tegra_xusb_remove_pads(padctl);
+			mutex_unlock(&padctl->lock);
+			return err;
+		}
+
+		if (!pad)
+			continue;
+
+		list_add_tail(&pad->list, &padctl->pads);
+	}
+
+	list_for_each_entry(pad, &padctl->pads, list)
+		tegra_xusb_pad_program(pad);
+
+	mutex_unlock(&padctl->lock);
+	return 0;
+}
+
+static bool tegra_xusb_lane_check(struct tegra_xusb_lane *lane,
+				  const char *function)
+{
+	const char *func = lane->soc->funcs[lane->function];
+
+	return strcmp(function, func) == 0;
+}
+
+struct tegra_xusb_lane *tegra_xusb_find_lane(struct tegra_xusb_padctl *padctl,
+					     const char *type,
+					     unsigned int index)
+{
+	struct tegra_xusb_lane *lane, *hit = ERR_PTR(-ENODEV);
+	char *name;
+
+	name = kasprintf(GFP_KERNEL, "%s-%u", type, index);
+	if (!name)
+		return ERR_PTR(-ENOMEM);
+
+	list_for_each_entry(lane, &padctl->lanes, list) {
+		if (strcmp(lane->soc->name, name) == 0) {
+			hit = lane;
+			break;
+		}
+	}
+
+	kfree(name);
+	return hit;
+}
+
+struct tegra_xusb_lane *
+tegra_xusb_port_find_lane(struct tegra_xusb_port *port,
+			  const struct tegra_xusb_lane_map *map,
+			  const char *function)
+{
+	struct tegra_xusb_lane *lane, *match = ERR_PTR(-ENODEV);
+
+	for (map = map; map->type; map++) {
+		if (port->index != map->port)
+			continue;
+
+		lane = tegra_xusb_find_lane(port->padctl, map->type,
+					    map->index);
+		if (IS_ERR(lane))
+			continue;
+
+		if (!tegra_xusb_lane_check(lane, function))
+			continue;
+
+		if (!IS_ERR(match))
+			dev_err(&port->dev, "conflicting match: %s-%u / %s\n",
+				map->type, map->index, match->soc->name);
+		else
+			match = lane;
+	}
+
+	return match;
+}
+
+static struct device_node *
+tegra_xusb_find_port_node(struct tegra_xusb_padctl *padctl, const char *type,
+			  unsigned int index)
+{
+	/*
+	 * of_find_node_by_name() drops a reference, so make sure to grab one.
+	 */
+	struct device_node *np = of_node_get(padctl->dev->of_node);
+
+	np = of_find_node_by_name(np, "ports");
+	if (np) {
+		char *name;
+
+		name = kasprintf(GFP_KERNEL, "%s-%u", type, index);
+		np = of_find_node_by_name(np, name);
+		kfree(name);
+	}
+
+	return np;
+}
+
+struct tegra_xusb_port *
+tegra_xusb_find_port(struct tegra_xusb_padctl *padctl, const char *type,
+		     unsigned int index)
+{
+	struct tegra_xusb_port *port;
+	struct device_node *np;
+
+	np = tegra_xusb_find_port_node(padctl, type, index);
+	if (!np)
+		return NULL;
+
+	list_for_each_entry(port, &padctl->ports, list) {
+		if (np == port->dev.of_node) {
+			of_node_put(np);
+			return port;
+		}
+	}
+
+	of_node_put(np);
+
+	return NULL;
+}
+
+struct tegra_xusb_usb2_port *
+tegra_xusb_find_usb2_port(struct tegra_xusb_padctl *padctl, unsigned int index)
+{
+	struct tegra_xusb_port *port;
+
+	port = tegra_xusb_find_port(padctl, "usb2", index);
+	if (port)
+		return to_usb2_port(port);
+
+	return NULL;
+}
+
+struct tegra_xusb_usb3_port *
+tegra_xusb_find_usb3_port(struct tegra_xusb_padctl *padctl, unsigned int index)
+{
+	struct tegra_xusb_port *port;
+
+	port = tegra_xusb_find_port(padctl, "usb3", index);
+	if (port)
+		return to_usb3_port(port);
+
+	return NULL;
+}
+
+static void tegra_xusb_port_release(struct device *dev)
+{
+}
+
+static struct device_type tegra_xusb_port_type = {
+	.release = tegra_xusb_port_release,
+};
+
+static int tegra_xusb_port_init(struct tegra_xusb_port *port,
+				struct tegra_xusb_padctl *padctl,
+				struct device_node *np,
+				const char *name,
+				unsigned int index)
+{
+	int err;
+
+	INIT_LIST_HEAD(&port->list);
+	port->padctl = padctl;
+	port->index = index;
+
+	device_initialize(&port->dev);
+	port->dev.type = &tegra_xusb_port_type;
+	port->dev.of_node = of_node_get(np);
+	port->dev.parent = padctl->dev;
+
+	err = dev_set_name(&port->dev, "%s-%u", name, index);
+	if (err < 0)
+		goto unregister;
+
+	err = device_add(&port->dev);
+	if (err < 0)
+		goto unregister;
+
+	return 0;
+
+unregister:
+	device_unregister(&port->dev);
+	return err;
+}
+
+static void tegra_xusb_port_unregister(struct tegra_xusb_port *port)
+{
+	device_unregister(&port->dev);
+}
+
+static int tegra_xusb_usb2_port_parse_dt(struct tegra_xusb_usb2_port *usb2)
+{
+	struct tegra_xusb_port *port = &usb2->base;
+	struct device_node *np = port->dev.of_node;
+
+	usb2->internal = of_property_read_bool(np, "nvidia,internal");
+
+	usb2->supply = devm_regulator_get(&port->dev, "vbus");
+	if (IS_ERR(usb2->supply))
+		return PTR_ERR(usb2->supply);
+
+	return 0;
+}
+
+static int tegra_xusb_add_usb2_port(struct tegra_xusb_padctl *padctl,
+				    unsigned int index)
+{
+	struct tegra_xusb_usb2_port *usb2;
+	struct device_node *np;
+	int err = 0;
+
+	/*
+	 * USB2 ports don't require additional properties, but if the port is
+	 * marked as disabled there is no reason to register it.
+	 */
+	np = tegra_xusb_find_port_node(padctl, "usb2", index);
+	if (!np || !of_device_is_available(np))
+		goto out;
+
+	usb2 = devm_kzalloc(padctl->dev, sizeof(*usb2), GFP_KERNEL);
+	if (!usb2) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	err = tegra_xusb_port_init(&usb2->base, padctl, np, "usb2", index);
+	if (err < 0)
+		goto out;
+
+	usb2->base.ops = padctl->soc->ports.usb2.ops;
+
+	usb2->base.lane = usb2->base.ops->map(&usb2->base);
+	if (IS_ERR(usb2->base.lane)) {
+		err = PTR_ERR(usb2->base.lane);
+		goto out;
+	}
+
+	err = tegra_xusb_usb2_port_parse_dt(usb2);
+	if (err < 0) {
+		tegra_xusb_port_unregister(&usb2->base);
+		goto out;
+	}
+
+	list_add_tail(&usb2->base.list, &padctl->ports);
+
+out:
+	of_node_put(np);
+	return err;
+}
+
+static int tegra_xusb_ulpi_port_parse_dt(struct tegra_xusb_ulpi_port *ulpi)
+{
+	struct tegra_xusb_port *port = &ulpi->base;
+	struct device_node *np = port->dev.of_node;
+
+	ulpi->internal = of_property_read_bool(np, "nvidia,internal");
+
+	return 0;
+}
+
+static int tegra_xusb_add_ulpi_port(struct tegra_xusb_padctl *padctl,
+				    unsigned int index)
+{
+	struct tegra_xusb_ulpi_port *ulpi;
+	struct device_node *np;
+	int err = 0;
+
+	np = tegra_xusb_find_port_node(padctl, "ulpi", index);
+	if (!np || !of_device_is_available(np))
+		goto out;
+
+	ulpi = devm_kzalloc(padctl->dev, sizeof(*ulpi), GFP_KERNEL);
+	if (!ulpi) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	err = tegra_xusb_port_init(&ulpi->base, padctl, np, "ulpi", index);
+	if (err < 0)
+		goto out;
+
+	ulpi->base.ops = padctl->soc->ports.ulpi.ops;
+
+	ulpi->base.lane = ulpi->base.ops->map(&ulpi->base);
+	if (IS_ERR(ulpi->base.lane)) {
+		err = PTR_ERR(ulpi->base.lane);
+		goto out;
+	}
+
+	err = tegra_xusb_ulpi_port_parse_dt(ulpi);
+	if (err < 0) {
+		tegra_xusb_port_unregister(&ulpi->base);
+		goto out;
+	}
+
+	list_add_tail(&ulpi->base.list, &padctl->ports);
+
+out:
+	of_node_put(np);
+	return err;
+}
+
+static int tegra_xusb_hsic_port_parse_dt(struct tegra_xusb_hsic_port *hsic)
+{
+	/* XXX */
+	return 0;
+}
+
+static int tegra_xusb_add_hsic_port(struct tegra_xusb_padctl *padctl,
+				    unsigned int index)
+{
+	struct tegra_xusb_hsic_port *hsic;
+	struct device_node *np;
+	int err = 0;
+
+	np = tegra_xusb_find_port_node(padctl, "hsic", index);
+	if (!np || !of_device_is_available(np))
+		goto out;
+
+	hsic = devm_kzalloc(padctl->dev, sizeof(*hsic), GFP_KERNEL);
+	if (!hsic) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	err = tegra_xusb_port_init(&hsic->base, padctl, np, "hsic", index);
+	if (err < 0)
+		goto out;
+
+	hsic->base.ops = padctl->soc->ports.hsic.ops;
+
+	hsic->base.lane = hsic->base.ops->map(&hsic->base);
+	if (IS_ERR(hsic->base.lane)) {
+		err = PTR_ERR(hsic->base.lane);
+		goto out;
+	}
+
+	err = tegra_xusb_hsic_port_parse_dt(hsic);
+	if (err < 0) {
+		tegra_xusb_port_unregister(&hsic->base);
+		goto out;
+	}
+
+	list_add_tail(&hsic->base.list, &padctl->ports);
+
+out:
+	of_node_put(np);
+	return err;
+}
+
+static int tegra_xusb_usb3_port_parse_dt(struct tegra_xusb_usb3_port *usb3)
+{
+	struct tegra_xusb_port *port = &usb3->base;
+	struct device_node *np = port->dev.of_node;
+	u32 value;
+	int err;
+
+	err = of_property_read_u32(np, "nvidia,usb2-companion", &value);
+	if (err < 0) {
+		dev_err(&port->dev, "failed to read port: %d\n", err);
+		return err;
+	}
+
+	usb3->port = value;
+
+	usb3->internal = of_property_read_bool(np, "nvidia,internal");
+
+	usb3->supply = devm_regulator_get(&port->dev, "vbus");
+	if (IS_ERR(usb3->supply))
+		return PTR_ERR(usb3->supply);
+
+	return 0;
+}
+
+static int tegra_xusb_add_usb3_port(struct tegra_xusb_padctl *padctl,
+				    unsigned int index)
+{
+	struct tegra_xusb_usb3_port *usb3;
+	struct device_node *np;
+	int err = 0;
+
+	/*
+	 * If there is no supplemental configuration in the device tree the
+	 * port is unusable. But it is valid to configure only a single port,
+	 * hence return 0 instead of an error to allow ports to be optional.
+	 */
+	np = tegra_xusb_find_port_node(padctl, "usb3", index);
+	if (!np || !of_device_is_available(np))
+		goto out;
+
+	usb3 = devm_kzalloc(padctl->dev, sizeof(*usb3), GFP_KERNEL);
+	if (!usb3) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	err = tegra_xusb_port_init(&usb3->base, padctl, np, "usb3", index);
+	if (err < 0)
+		goto out;
+
+	usb3->base.ops = padctl->soc->ports.usb3.ops;
+
+	usb3->base.lane = usb3->base.ops->map(&usb3->base);
+	if (IS_ERR(usb3->base.lane)) {
+		err = PTR_ERR(usb3->base.lane);
+		goto out;
+	}
+
+	err = tegra_xusb_usb3_port_parse_dt(usb3);
+	if (err < 0) {
+		tegra_xusb_port_unregister(&usb3->base);
+		goto out;
+	}
+
+	list_add_tail(&usb3->base.list, &padctl->ports);
+
+out:
+	of_node_put(np);
+	return err;
+}
+
+static void __tegra_xusb_remove_ports(struct tegra_xusb_padctl *padctl)
+{
+	struct tegra_xusb_port *port, *tmp;
+
+	list_for_each_entry_safe_reverse(port, tmp, &padctl->ports, list) {
+		list_del(&port->list);
+		tegra_xusb_port_unregister(port);
+	}
+}
+
+static int tegra_xusb_setup_ports(struct tegra_xusb_padctl *padctl)
+{
+	struct tegra_xusb_port *port;
+	unsigned int i;
+	int err = 0;
+
+	mutex_lock(&padctl->lock);
+
+	for (i = 0; i < padctl->soc->ports.usb2.count; i++) {
+		err = tegra_xusb_add_usb2_port(padctl, i);
+		if (err < 0)
+			goto remove_ports;
+	}
+
+	for (i = 0; i < padctl->soc->ports.ulpi.count; i++) {
+		err = tegra_xusb_add_ulpi_port(padctl, i);
+		if (err < 0)
+			goto remove_ports;
+	}
+
+	for (i = 0; i < padctl->soc->ports.hsic.count; i++) {
+		err = tegra_xusb_add_hsic_port(padctl, i);
+		if (err < 0)
+			goto remove_ports;
+	}
+
+	for (i = 0; i < padctl->soc->ports.usb3.count; i++) {
+		err = tegra_xusb_add_usb3_port(padctl, i);
+		if (err < 0)
+			goto remove_ports;
+	}
+
+	list_for_each_entry(port, &padctl->ports, list) {
+		err = port->ops->enable(port);
+		if (err < 0)
+			dev_err(padctl->dev, "failed to enable port %s: %d\n",
+				dev_name(&port->dev), err);
+	}
+
+	goto unlock;
+
+remove_ports:
+	__tegra_xusb_remove_ports(padctl);
+unlock:
+	mutex_unlock(&padctl->lock);
+	return err;
+}
+
+static void tegra_xusb_remove_ports(struct tegra_xusb_padctl *padctl)
+{
+	mutex_lock(&padctl->lock);
+	__tegra_xusb_remove_ports(padctl);
+	mutex_unlock(&padctl->lock);
+}
+
+static int tegra_xusb_padctl_probe(struct platform_device *pdev)
+{
+	struct device_node *np = of_node_get(pdev->dev.of_node);
+	const struct tegra_xusb_padctl_soc *soc;
+	struct tegra_xusb_padctl *padctl;
+	const struct of_device_id *match;
+	struct resource *res;
+	int err;
+
+	/* for backwards compatibility with old device trees */
+	np = of_find_node_by_name(np, "pads");
+	if (!np) {
+		dev_warn(&pdev->dev, "deprecated DT, using legacy driver\n");
+		return tegra_xusb_padctl_legacy_probe(pdev);
+	}
+
+	of_node_put(np);
+
+	match = of_match_node(tegra_xusb_padctl_of_match, pdev->dev.of_node);
+	soc = match->data;
+
+	padctl = soc->ops->probe(&pdev->dev, soc);
+	if (IS_ERR(padctl))
+		return PTR_ERR(padctl);
+
+	platform_set_drvdata(pdev, padctl);
+	INIT_LIST_HEAD(&padctl->ports);
+	INIT_LIST_HEAD(&padctl->lanes);
+	INIT_LIST_HEAD(&padctl->pads);
+	mutex_init(&padctl->lock);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	padctl->regs = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(padctl->regs)) {
+		err = PTR_ERR(padctl->regs);
+		goto remove;
+	}
+
+	padctl->rst = devm_reset_control_get(&pdev->dev, NULL);
+	if (IS_ERR(padctl->rst)) {
+		err = PTR_ERR(padctl->rst);
+		goto remove;
+	}
+
+	err = reset_control_deassert(padctl->rst);
+	if (err < 0)
+		goto remove;
+
+	err = tegra_xusb_setup_pads(padctl);
+	if (err < 0) {
+		dev_err(&pdev->dev, "failed to setup pads: %d\n", err);
+		goto reset;
+	}
+
+	err = tegra_xusb_setup_ports(padctl);
+	if (err) {
+		dev_err(&pdev->dev, "failed to setup XUSB ports: %d\n", err);
+		goto remove_pads;
+	}
+
+	return 0;
+
+remove_pads:
+	tegra_xusb_remove_pads(padctl);
+reset:
+	reset_control_assert(padctl->rst);
+remove:
+	soc->ops->remove(padctl);
+	return err;
+}
+
+static int tegra_xusb_padctl_remove(struct platform_device *pdev)
+{
+	struct tegra_xusb_padctl *padctl = platform_get_drvdata(pdev);
+	int err;
+
+	tegra_xusb_remove_ports(padctl);
+	tegra_xusb_remove_pads(padctl);
+
+	err = reset_control_assert(padctl->rst);
+	if (err < 0)
+		dev_err(&pdev->dev, "failed to assert reset: %d\n", err);
+
+	padctl->soc->ops->remove(padctl);
+
+	return err;
+}
+
+static struct platform_driver tegra_xusb_padctl_driver = {
+	.driver = {
+		.name = "tegra-xusb-padctl",
+		.of_match_table = tegra_xusb_padctl_of_match,
+	},
+	.probe = tegra_xusb_padctl_probe,
+	.remove = tegra_xusb_padctl_remove,
+};
+module_platform_driver(tegra_xusb_padctl_driver);
+
+struct tegra_xusb_padctl *tegra_xusb_padctl_get(struct device *dev)
+{
+	struct tegra_xusb_padctl *padctl;
+	struct platform_device *pdev;
+	struct device_node *np;
+
+	np = of_parse_phandle(dev->of_node, "nvidia,xusb-padctl", 0);
+	if (!np)
+		return ERR_PTR(-EINVAL);
+
+	/*
+	 * This is slightly ugly. A better implementation would be to keep a
+	 * registry of pad controllers, but since there will almost certainly
+	 * only ever be one per SoC that would be a little overkill.
+	 */
+	pdev = of_find_device_by_node(np);
+	if (!pdev) {
+		of_node_put(np);
+		return ERR_PTR(-ENODEV);
+	}
+
+	of_node_put(np);
+
+	padctl = platform_get_drvdata(pdev);
+	if (!padctl) {
+		put_device(&pdev->dev);
+		return ERR_PTR(-EPROBE_DEFER);
+	}
+
+	return padctl;
+}
+EXPORT_SYMBOL_GPL(tegra_xusb_padctl_get);
+
+void tegra_xusb_padctl_put(struct tegra_xusb_padctl *padctl)
+{
+	if (padctl)
+		put_device(padctl->dev);
+}
+EXPORT_SYMBOL_GPL(tegra_xusb_padctl_put);
+
+int tegra_xusb_padctl_usb3_save_context(struct tegra_xusb_padctl *padctl,
+					unsigned int port)
+{
+	if (padctl->soc->ops->usb3_save_context)
+		return padctl->soc->ops->usb3_save_context(padctl, port);
+
+	return -ENOSYS;
+}
+EXPORT_SYMBOL_GPL(tegra_xusb_padctl_usb3_save_context);
+
+int tegra_xusb_padctl_hsic_set_idle(struct tegra_xusb_padctl *padctl,
+				    unsigned int port, bool idle)
+{
+	if (padctl->soc->ops->hsic_set_idle)
+		return padctl->soc->ops->hsic_set_idle(padctl, port, idle);
+
+	return -ENOSYS;
+}
+EXPORT_SYMBOL_GPL(tegra_xusb_padctl_hsic_set_idle);
+
+int tegra_xusb_padctl_usb3_set_lfps_detect(struct tegra_xusb_padctl *padctl,
+					   unsigned int port, bool enable)
+{
+	if (padctl->soc->ops->usb3_set_lfps_detect)
+		return padctl->soc->ops->usb3_set_lfps_detect(padctl, port,
+							      enable);
+
+	return -ENOSYS;
+}
+EXPORT_SYMBOL_GPL(tegra_xusb_padctl_usb3_set_lfps_detect);
+
+MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
+MODULE_DESCRIPTION("Tegra XUSB Pad Controller driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/tegra/xusb.h b/drivers/phy/tegra/xusb.h
new file mode 100644
index 0000000..b49dbc3
--- /dev/null
+++ b/drivers/phy/tegra/xusb.h
@@ -0,0 +1,421 @@
+/*
+ * Copyright (c) 2014-2015, NVIDIA CORPORATION.  All rights reserved.
+ * Copyright (c) 2015, Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.
+ */
+
+#ifndef __PHY_TEGRA_XUSB_H
+#define __PHY_TEGRA_XUSB_H
+
+#include <linux/io.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+
+/* legacy entry points for backwards-compatibility */
+int tegra_xusb_padctl_legacy_probe(struct platform_device *pdev);
+int tegra_xusb_padctl_legacy_remove(struct platform_device *pdev);
+
+struct phy;
+struct phy_provider;
+struct platform_device;
+struct regulator;
+
+/*
+ * lanes
+ */
+struct tegra_xusb_lane_soc {
+	const char *name;
+
+	unsigned int offset;
+	unsigned int shift;
+	unsigned int mask;
+
+	const char * const *funcs;
+	unsigned int num_funcs;
+};
+
+struct tegra_xusb_lane {
+	const struct tegra_xusb_lane_soc *soc;
+	struct tegra_xusb_pad *pad;
+	struct device_node *np;
+	struct list_head list;
+	unsigned int function;
+	unsigned int index;
+};
+
+int tegra_xusb_lane_parse_dt(struct tegra_xusb_lane *lane,
+			     struct device_node *np);
+
+struct tegra_xusb_usb2_lane {
+	struct tegra_xusb_lane base;
+
+	u32 hs_curr_level_offset;
+};
+
+static inline struct tegra_xusb_usb2_lane *
+to_usb2_lane(struct tegra_xusb_lane *lane)
+{
+	return container_of(lane, struct tegra_xusb_usb2_lane, base);
+}
+
+struct tegra_xusb_ulpi_lane {
+	struct tegra_xusb_lane base;
+};
+
+static inline struct tegra_xusb_ulpi_lane *
+to_ulpi_lane(struct tegra_xusb_lane *lane)
+{
+	return container_of(lane, struct tegra_xusb_ulpi_lane, base);
+}
+
+struct tegra_xusb_hsic_lane {
+	struct tegra_xusb_lane base;
+
+	u32 strobe_trim;
+	u32 rx_strobe_trim;
+	u32 rx_data_trim;
+	u32 tx_rtune_n;
+	u32 tx_rtune_p;
+	u32 tx_rslew_n;
+	u32 tx_rslew_p;
+	bool auto_term;
+};
+
+static inline struct tegra_xusb_hsic_lane *
+to_hsic_lane(struct tegra_xusb_lane *lane)
+{
+	return container_of(lane, struct tegra_xusb_hsic_lane, base);
+}
+
+struct tegra_xusb_pcie_lane {
+	struct tegra_xusb_lane base;
+};
+
+static inline struct tegra_xusb_pcie_lane *
+to_pcie_lane(struct tegra_xusb_lane *lane)
+{
+	return container_of(lane, struct tegra_xusb_pcie_lane, base);
+}
+
+struct tegra_xusb_sata_lane {
+	struct tegra_xusb_lane base;
+};
+
+static inline struct tegra_xusb_sata_lane *
+to_sata_lane(struct tegra_xusb_lane *lane)
+{
+	return container_of(lane, struct tegra_xusb_sata_lane, base);
+}
+
+struct tegra_xusb_lane_ops {
+	struct tegra_xusb_lane *(*probe)(struct tegra_xusb_pad *pad,
+					 struct device_node *np,
+					 unsigned int index);
+	void (*remove)(struct tegra_xusb_lane *lane);
+};
+
+/*
+ * pads
+ */
+struct tegra_xusb_pad_soc;
+struct tegra_xusb_padctl;
+
+struct tegra_xusb_pad_ops {
+	struct tegra_xusb_pad *(*probe)(struct tegra_xusb_padctl *padctl,
+					const struct tegra_xusb_pad_soc *soc,
+					struct device_node *np);
+	void (*remove)(struct tegra_xusb_pad *pad);
+};
+
+struct tegra_xusb_pad_soc {
+	const char *name;
+
+	const struct tegra_xusb_lane_soc *lanes;
+	unsigned int num_lanes;
+
+	const struct tegra_xusb_pad_ops *ops;
+};
+
+struct tegra_xusb_pad {
+	const struct tegra_xusb_pad_soc *soc;
+	struct tegra_xusb_padctl *padctl;
+	struct phy_provider *provider;
+	struct phy **lanes;
+	struct device dev;
+
+	const struct tegra_xusb_lane_ops *ops;
+
+	struct list_head list;
+};
+
+static inline struct tegra_xusb_pad *to_tegra_xusb_pad(struct device *dev)
+{
+	return container_of(dev, struct tegra_xusb_pad, dev);
+}
+
+int tegra_xusb_pad_init(struct tegra_xusb_pad *pad,
+			struct tegra_xusb_padctl *padctl,
+			struct device_node *np);
+int tegra_xusb_pad_register(struct tegra_xusb_pad *pad,
+			    const struct phy_ops *ops);
+void tegra_xusb_pad_unregister(struct tegra_xusb_pad *pad);
+
+struct tegra_xusb_usb2_pad {
+	struct tegra_xusb_pad base;
+
+	struct clk *clk;
+	unsigned int enable;
+	struct mutex lock;
+};
+
+static inline struct tegra_xusb_usb2_pad *
+to_usb2_pad(struct tegra_xusb_pad *pad)
+{
+	return container_of(pad, struct tegra_xusb_usb2_pad, base);
+}
+
+struct tegra_xusb_ulpi_pad {
+	struct tegra_xusb_pad base;
+};
+
+static inline struct tegra_xusb_ulpi_pad *
+to_ulpi_pad(struct tegra_xusb_pad *pad)
+{
+	return container_of(pad, struct tegra_xusb_ulpi_pad, base);
+}
+
+struct tegra_xusb_hsic_pad {
+	struct tegra_xusb_pad base;
+
+	struct regulator *supply;
+	struct clk *clk;
+};
+
+static inline struct tegra_xusb_hsic_pad *
+to_hsic_pad(struct tegra_xusb_pad *pad)
+{
+	return container_of(pad, struct tegra_xusb_hsic_pad, base);
+}
+
+struct tegra_xusb_pcie_pad {
+	struct tegra_xusb_pad base;
+
+	struct reset_control *rst;
+	struct clk *pll;
+
+	unsigned int enable;
+};
+
+static inline struct tegra_xusb_pcie_pad *
+to_pcie_pad(struct tegra_xusb_pad *pad)
+{
+	return container_of(pad, struct tegra_xusb_pcie_pad, base);
+}
+
+struct tegra_xusb_sata_pad {
+	struct tegra_xusb_pad base;
+
+	struct reset_control *rst;
+	struct clk *pll;
+
+	unsigned int enable;
+};
+
+static inline struct tegra_xusb_sata_pad *
+to_sata_pad(struct tegra_xusb_pad *pad)
+{
+	return container_of(pad, struct tegra_xusb_sata_pad, base);
+}
+
+/*
+ * ports
+ */
+struct tegra_xusb_port_ops;
+
+struct tegra_xusb_port {
+	struct tegra_xusb_padctl *padctl;
+	struct tegra_xusb_lane *lane;
+	unsigned int index;
+
+	struct list_head list;
+	struct device dev;
+
+	const struct tegra_xusb_port_ops *ops;
+};
+
+struct tegra_xusb_lane_map {
+	unsigned int port;
+	const char *type;
+	unsigned int index;
+	const char *func;
+};
+
+struct tegra_xusb_lane *
+tegra_xusb_port_find_lane(struct tegra_xusb_port *port,
+			  const struct tegra_xusb_lane_map *map,
+			  const char *function);
+
+struct tegra_xusb_port *
+tegra_xusb_find_port(struct tegra_xusb_padctl *padctl, const char *type,
+		     unsigned int index);
+
+struct tegra_xusb_usb2_port {
+	struct tegra_xusb_port base;
+
+	struct regulator *supply;
+	bool internal;
+};
+
+static inline struct tegra_xusb_usb2_port *
+to_usb2_port(struct tegra_xusb_port *port)
+{
+	return container_of(port, struct tegra_xusb_usb2_port, base);
+}
+
+struct tegra_xusb_usb2_port *
+tegra_xusb_find_usb2_port(struct tegra_xusb_padctl *padctl,
+			  unsigned int index);
+
+struct tegra_xusb_ulpi_port {
+	struct tegra_xusb_port base;
+
+	struct regulator *supply;
+	bool internal;
+};
+
+static inline struct tegra_xusb_ulpi_port *
+to_ulpi_port(struct tegra_xusb_port *port)
+{
+	return container_of(port, struct tegra_xusb_ulpi_port, base);
+}
+
+struct tegra_xusb_hsic_port {
+	struct tegra_xusb_port base;
+};
+
+static inline struct tegra_xusb_hsic_port *
+to_hsic_port(struct tegra_xusb_port *port)
+{
+	return container_of(port, struct tegra_xusb_hsic_port, base);
+}
+
+struct tegra_xusb_usb3_port {
+	struct tegra_xusb_port base;
+	struct regulator *supply;
+	bool context_saved;
+	unsigned int port;
+	bool internal;
+
+	u32 tap1;
+	u32 amp;
+	u32 ctle_z;
+	u32 ctle_g;
+};
+
+static inline struct tegra_xusb_usb3_port *
+to_usb3_port(struct tegra_xusb_port *port)
+{
+	return container_of(port, struct tegra_xusb_usb3_port, base);
+}
+
+struct tegra_xusb_usb3_port *
+tegra_xusb_find_usb3_port(struct tegra_xusb_padctl *padctl,
+			  unsigned int index);
+
+struct tegra_xusb_port_ops {
+	int (*enable)(struct tegra_xusb_port *port);
+	void (*disable)(struct tegra_xusb_port *port);
+	struct tegra_xusb_lane *(*map)(struct tegra_xusb_port *port);
+};
+
+/*
+ * pad controller
+ */
+struct tegra_xusb_padctl_soc;
+
+struct tegra_xusb_padctl_ops {
+	struct tegra_xusb_padctl *
+		(*probe)(struct device *dev,
+			 const struct tegra_xusb_padctl_soc *soc);
+	void (*remove)(struct tegra_xusb_padctl *padctl);
+
+	int (*usb3_save_context)(struct tegra_xusb_padctl *padctl,
+				 unsigned int index);
+	int (*hsic_set_idle)(struct tegra_xusb_padctl *padctl,
+			     unsigned int index, bool idle);
+	int (*usb3_set_lfps_detect)(struct tegra_xusb_padctl *padctl,
+				    unsigned int index, bool enable);
+};
+
+struct tegra_xusb_padctl_soc {
+	const struct tegra_xusb_pad_soc * const *pads;
+	unsigned int num_pads;
+
+	struct {
+		struct {
+			const struct tegra_xusb_port_ops *ops;
+			unsigned int count;
+		} usb2, ulpi, hsic, usb3;
+	} ports;
+
+	const struct tegra_xusb_padctl_ops *ops;
+};
+
+struct tegra_xusb_padctl {
+	struct device *dev;
+	void __iomem *regs;
+	struct mutex lock;
+	struct reset_control *rst;
+
+	const struct tegra_xusb_padctl_soc *soc;
+
+	struct tegra_xusb_pad *pcie;
+	struct tegra_xusb_pad *sata;
+	struct tegra_xusb_pad *ulpi;
+	struct tegra_xusb_pad *usb2;
+	struct tegra_xusb_pad *hsic;
+
+	struct list_head ports;
+	struct list_head lanes;
+	struct list_head pads;
+
+	unsigned int enable;
+
+	struct clk *clk;
+};
+
+static inline void padctl_writel(struct tegra_xusb_padctl *padctl, u32 value,
+				 unsigned long offset)
+{
+	dev_dbg(padctl->dev, "%08lx < %08x\n", offset, value);
+	writel(value, padctl->regs + offset);
+}
+
+static inline u32 padctl_readl(struct tegra_xusb_padctl *padctl,
+			       unsigned long offset)
+{
+	u32 value = readl(padctl->regs + offset);
+	dev_dbg(padctl->dev, "%08lx > %08x\n", offset, value);
+	return value;
+}
+
+struct tegra_xusb_lane *tegra_xusb_find_lane(struct tegra_xusb_padctl *padctl,
+					     const char *name,
+					     unsigned int index);
+
+#if defined(CONFIG_ARCH_TEGRA_124_SOC) || defined(CONFIG_ARCH_TEGRA_132_SOC)
+extern const struct tegra_xusb_padctl_soc tegra124_xusb_padctl_soc;
+#endif
+#if defined(CONFIG_ARCH_TEGRA_210_SOC)
+extern const struct tegra_xusb_padctl_soc tegra210_xusb_padctl_soc;
+#endif
+
+#endif /* __PHY_TEGRA_XUSB_H */
diff --git a/drivers/pinctrl/tegra/pinctrl-tegra-xusb.c b/drivers/pinctrl/tegra/pinctrl-tegra-xusb.c
index 2f06029..946cda3 100644
--- a/drivers/pinctrl/tegra/pinctrl-tegra-xusb.c
+++ b/drivers/pinctrl/tegra/pinctrl-tegra-xusb.c
@@ -873,7 +873,7 @@
 };
 MODULE_DEVICE_TABLE(of, tegra_xusb_padctl_of_match);
 
-static int tegra_xusb_padctl_probe(struct platform_device *pdev)
+int tegra_xusb_padctl_legacy_probe(struct platform_device *pdev)
 {
 	struct tegra_xusb_padctl *padctl;
 	const struct of_device_id *match;
@@ -955,8 +955,9 @@
 	reset_control_assert(padctl->rst);
 	return err;
 }
+EXPORT_SYMBOL_GPL(tegra_xusb_padctl_legacy_probe);
 
-static int tegra_xusb_padctl_remove(struct platform_device *pdev)
+int tegra_xusb_padctl_legacy_remove(struct platform_device *pdev)
 {
 	struct tegra_xusb_padctl *padctl = platform_get_drvdata(pdev);
 	int err;
@@ -969,17 +970,4 @@
 
 	return err;
 }
-
-static struct platform_driver tegra_xusb_padctl_driver = {
-	.driver = {
-		.name = "tegra-xusb-padctl",
-		.of_match_table = tegra_xusb_padctl_of_match,
-	},
-	.probe = tegra_xusb_padctl_probe,
-	.remove = tegra_xusb_padctl_remove,
-};
-module_platform_driver(tegra_xusb_padctl_driver);
-
-MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
-MODULE_DESCRIPTION("Tegra 124 XUSB Pad Control driver");
-MODULE_LICENSE("GPL v2");
+EXPORT_SYMBOL_GPL(tegra_xusb_padctl_legacy_remove);
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 3050b18b..6acac62 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -69,6 +69,15 @@
 	  Say 'Y' to enable the support for the xHCI host controller
 	  found in Renesas R-Car ARM SoCs.
 
+config USB_XHCI_TEGRA
+	tristate "xHCI support for NVIDIA Tegra SoCs"
+	depends on PHY_TEGRA_XUSB
+	depends on RESET_CONTROLLER
+	select FW_LOADER
+	---help---
+	  Say 'Y' to enable the support for the xHCI host controller
+	  found in NVIDIA Tegra124 and later SoCs.
+
 endif # USB_XHCI_HCD
 
 config USB_EHCI_HCD
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index a9ddd3c..6ef785b 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -68,6 +68,7 @@
 obj-$(CONFIG_USB_XHCI_PCI)	+= xhci-pci.o
 obj-$(CONFIG_USB_XHCI_PLATFORM) += xhci-plat-hcd.o
 obj-$(CONFIG_USB_XHCI_MTK)	+= xhci-mtk.o
+obj-$(CONFIG_USB_XHCI_TEGRA)	+= xhci-tegra.o
 obj-$(CONFIG_USB_SL811_HCD)	+= sl811-hcd.o
 obj-$(CONFIG_USB_SL811_CS)	+= sl811_cs.o
 obj-$(CONFIG_USB_U132_HCD)	+= u132-hcd.o
diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c
new file mode 100644
index 0000000..0f53ae0
--- /dev/null
+++ b/drivers/usb/host/xhci-tegra.c
@@ -0,0 +1,1331 @@
+/*
+ * NVIDIA Tegra xHCI host controller driver
+ *
+ * Copyright (C) 2014 NVIDIA Corporation
+ * Copyright (C) 2014 Google, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/phy/tegra/xusb.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+
+#include "xhci.h"
+
+#define TEGRA_XHCI_SS_HIGH_SPEED 120000000
+#define TEGRA_XHCI_SS_LOW_SPEED   12000000
+
+/* FPCI CFG registers */
+#define XUSB_CFG_1				0x004
+#define  XUSB_IO_SPACE_EN			BIT(0)
+#define  XUSB_MEM_SPACE_EN			BIT(1)
+#define  XUSB_BUS_MASTER_EN			BIT(2)
+#define XUSB_CFG_4				0x010
+#define  XUSB_BASE_ADDR_SHIFT			15
+#define  XUSB_BASE_ADDR_MASK			0x1ffff
+#define XUSB_CFG_ARU_C11_CSBRANGE		0x41c
+#define XUSB_CFG_CSB_BASE_ADDR			0x800
+
+/* FPCI mailbox registers */
+#define XUSB_CFG_ARU_MBOX_CMD			0x0e4
+#define  MBOX_DEST_FALC				BIT(27)
+#define  MBOX_DEST_PME				BIT(28)
+#define  MBOX_DEST_SMI				BIT(29)
+#define  MBOX_DEST_XHCI				BIT(30)
+#define  MBOX_INT_EN				BIT(31)
+#define XUSB_CFG_ARU_MBOX_DATA_IN		0x0e8
+#define  CMD_DATA_SHIFT				0
+#define  CMD_DATA_MASK				0xffffff
+#define  CMD_TYPE_SHIFT				24
+#define  CMD_TYPE_MASK				0xff
+#define XUSB_CFG_ARU_MBOX_DATA_OUT		0x0ec
+#define XUSB_CFG_ARU_MBOX_OWNER			0x0f0
+#define  MBOX_OWNER_NONE			0
+#define  MBOX_OWNER_FW				1
+#define  MBOX_OWNER_SW				2
+#define XUSB_CFG_ARU_SMI_INTR			0x428
+#define  MBOX_SMI_INTR_FW_HANG			BIT(1)
+#define  MBOX_SMI_INTR_EN			BIT(3)
+
+/* IPFS registers */
+#define IPFS_XUSB_HOST_CONFIGURATION_0		0x180
+#define  IPFS_EN_FPCI				BIT(0)
+#define IPFS_XUSB_HOST_INTR_MASK_0		0x188
+#define  IPFS_IP_INT_MASK			BIT(16)
+#define IPFS_XUSB_HOST_CLKGATE_HYSTERESIS_0	0x1bc
+
+#define CSB_PAGE_SELECT_MASK			0x7fffff
+#define CSB_PAGE_SELECT_SHIFT			9
+#define CSB_PAGE_OFFSET_MASK			0x1ff
+#define CSB_PAGE_SELECT(addr)	((addr) >> (CSB_PAGE_SELECT_SHIFT) &	\
+				 CSB_PAGE_SELECT_MASK)
+#define CSB_PAGE_OFFSET(addr)	((addr) & CSB_PAGE_OFFSET_MASK)
+
+/* Falcon CSB registers */
+#define XUSB_FALC_CPUCTL			0x100
+#define  CPUCTL_STARTCPU			BIT(1)
+#define  CPUCTL_STATE_HALTED			BIT(4)
+#define  CPUCTL_STATE_STOPPED			BIT(5)
+#define XUSB_FALC_BOOTVEC			0x104
+#define XUSB_FALC_DMACTL			0x10c
+#define XUSB_FALC_IMFILLRNG1			0x154
+#define  IMFILLRNG1_TAG_MASK			0xffff
+#define  IMFILLRNG1_TAG_LO_SHIFT		0
+#define  IMFILLRNG1_TAG_HI_SHIFT		16
+#define XUSB_FALC_IMFILLCTL			0x158
+
+/* MP CSB registers */
+#define XUSB_CSB_MP_ILOAD_ATTR			0x101a00
+#define XUSB_CSB_MP_ILOAD_BASE_LO		0x101a04
+#define XUSB_CSB_MP_ILOAD_BASE_HI		0x101a08
+#define XUSB_CSB_MP_L2IMEMOP_SIZE		0x101a10
+#define  L2IMEMOP_SIZE_SRC_OFFSET_SHIFT		8
+#define  L2IMEMOP_SIZE_SRC_OFFSET_MASK		0x3ff
+#define  L2IMEMOP_SIZE_SRC_COUNT_SHIFT		24
+#define  L2IMEMOP_SIZE_SRC_COUNT_MASK		0xff
+#define XUSB_CSB_MP_L2IMEMOP_TRIG		0x101a14
+#define  L2IMEMOP_ACTION_SHIFT			24
+#define  L2IMEMOP_INVALIDATE_ALL		(0x40 << L2IMEMOP_ACTION_SHIFT)
+#define  L2IMEMOP_LOAD_LOCKED_RESULT		(0x11 << L2IMEMOP_ACTION_SHIFT)
+#define XUSB_CSB_MP_APMAP			0x10181c
+#define  APMAP_BOOTPATH				BIT(31)
+
+#define IMEM_BLOCK_SIZE				256
+
+struct tegra_xusb_fw_header {
+	u32 boot_loadaddr_in_imem;
+	u32 boot_codedfi_offset;
+	u32 boot_codetag;
+	u32 boot_codesize;
+	u32 phys_memaddr;
+	u16 reqphys_memsize;
+	u16 alloc_phys_memsize;
+	u32 rodata_img_offset;
+	u32 rodata_section_start;
+	u32 rodata_section_end;
+	u32 main_fnaddr;
+	u32 fwimg_cksum;
+	u32 fwimg_created_time;
+	u32 imem_resident_start;
+	u32 imem_resident_end;
+	u32 idirect_start;
+	u32 idirect_end;
+	u32 l2_imem_start;
+	u32 l2_imem_end;
+	u32 version_id;
+	u8 init_ddirect;
+	u8 reserved[3];
+	u32 phys_addr_log_buffer;
+	u32 total_log_entries;
+	u32 dequeue_ptr;
+	u32 dummy_var[2];
+	u32 fwimg_len;
+	u8 magic[8];
+	u32 ss_low_power_entry_timeout;
+	u8 num_hsic_port;
+	u8 padding[139]; /* Pad to 256 bytes */
+};
+
+struct tegra_xusb_phy_type {
+	const char *name;
+	unsigned int num;
+};
+
+struct tegra_xusb_soc {
+	const char *firmware;
+	const char * const *supply_names;
+	unsigned int num_supplies;
+	const struct tegra_xusb_phy_type *phy_types;
+	unsigned int num_types;
+
+	struct {
+		struct {
+			unsigned int offset;
+			unsigned int count;
+		} usb2, ulpi, hsic, usb3;
+	} ports;
+
+	bool scale_ss_clock;
+};
+
+struct tegra_xusb {
+	struct device *dev;
+	void __iomem *regs;
+	struct usb_hcd *hcd;
+
+	struct mutex lock;
+
+	int xhci_irq;
+	int mbox_irq;
+
+	void __iomem *ipfs_base;
+	void __iomem *fpci_base;
+
+	const struct tegra_xusb_soc *soc;
+
+	struct regulator_bulk_data *supplies;
+
+	struct tegra_xusb_padctl *padctl;
+
+	struct clk *host_clk;
+	struct clk *falcon_clk;
+	struct clk *ss_clk;
+	struct clk *ss_src_clk;
+	struct clk *hs_src_clk;
+	struct clk *fs_src_clk;
+	struct clk *pll_u_480m;
+	struct clk *clk_m;
+	struct clk *pll_e;
+
+	struct reset_control *host_rst;
+	struct reset_control *ss_rst;
+
+	struct phy **phys;
+	unsigned int num_phys;
+
+	/* Firmware loading related */
+	struct {
+		size_t size;
+		void *virt;
+		dma_addr_t phys;
+	} fw;
+};
+
+static struct hc_driver __read_mostly tegra_xhci_hc_driver;
+
+static inline u32 fpci_readl(struct tegra_xusb *tegra, unsigned int offset)
+{
+	return readl(tegra->fpci_base + offset);
+}
+
+static inline void fpci_writel(struct tegra_xusb *tegra, u32 value,
+			       unsigned int offset)
+{
+	writel(value, tegra->fpci_base + offset);
+}
+
+static inline u32 ipfs_readl(struct tegra_xusb *tegra, unsigned int offset)
+{
+	return readl(tegra->ipfs_base + offset);
+}
+
+static inline void ipfs_writel(struct tegra_xusb *tegra, u32 value,
+			       unsigned int offset)
+{
+	writel(value, tegra->ipfs_base + offset);
+}
+
+static u32 csb_readl(struct tegra_xusb *tegra, unsigned int offset)
+{
+	u32 page = CSB_PAGE_SELECT(offset);
+	u32 ofs = CSB_PAGE_OFFSET(offset);
+
+	fpci_writel(tegra, page, XUSB_CFG_ARU_C11_CSBRANGE);
+
+	return fpci_readl(tegra, XUSB_CFG_CSB_BASE_ADDR + ofs);
+}
+
+static void csb_writel(struct tegra_xusb *tegra, u32 value,
+		       unsigned int offset)
+{
+	u32 page = CSB_PAGE_SELECT(offset);
+	u32 ofs = CSB_PAGE_OFFSET(offset);
+
+	fpci_writel(tegra, page, XUSB_CFG_ARU_C11_CSBRANGE);
+	fpci_writel(tegra, value, XUSB_CFG_CSB_BASE_ADDR + ofs);
+}
+
+static int tegra_xusb_set_ss_clk(struct tegra_xusb *tegra,
+				 unsigned long rate)
+{
+	unsigned long new_parent_rate, old_parent_rate;
+	struct clk *clk = tegra->ss_src_clk;
+	unsigned int div;
+	int err;
+
+	if (clk_get_rate(clk) == rate)
+		return 0;
+
+	switch (rate) {
+	case TEGRA_XHCI_SS_HIGH_SPEED:
+		/*
+		 * Reparent to PLLU_480M. Set divider first to avoid
+		 * overclocking.
+		 */
+		old_parent_rate = clk_get_rate(clk_get_parent(clk));
+		new_parent_rate = clk_get_rate(tegra->pll_u_480m);
+		div = new_parent_rate / rate;
+
+		err = clk_set_rate(clk, old_parent_rate / div);
+		if (err)
+			return err;
+
+		err = clk_set_parent(clk, tegra->pll_u_480m);
+		if (err)
+			return err;
+
+		/*
+		 * The rate should already be correct, but set it again just
+		 * to be sure.
+		 */
+		err = clk_set_rate(clk, rate);
+		if (err)
+			return err;
+
+		break;
+
+	case TEGRA_XHCI_SS_LOW_SPEED:
+		/* Reparent to CLK_M */
+		err = clk_set_parent(clk, tegra->clk_m);
+		if (err)
+			return err;
+
+		err = clk_set_rate(clk, rate);
+		if (err)
+			return err;
+
+		break;
+
+	default:
+		dev_err(tegra->dev, "Invalid SS rate: %lu Hz\n", rate);
+		return -EINVAL;
+	}
+
+	if (clk_get_rate(clk) != rate) {
+		dev_err(tegra->dev, "SS clock doesn't match requested rate\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static unsigned long extract_field(u32 value, unsigned int start,
+				   unsigned int count)
+{
+	return (value >> start) & ((1 << count) - 1);
+}
+
+/* Command requests from the firmware */
+enum tegra_xusb_mbox_cmd {
+	MBOX_CMD_MSG_ENABLED = 1,
+	MBOX_CMD_INC_FALC_CLOCK,
+	MBOX_CMD_DEC_FALC_CLOCK,
+	MBOX_CMD_INC_SSPI_CLOCK,
+	MBOX_CMD_DEC_SSPI_CLOCK,
+	MBOX_CMD_SET_BW, /* no ACK/NAK required */
+	MBOX_CMD_SET_SS_PWR_GATING,
+	MBOX_CMD_SET_SS_PWR_UNGATING,
+	MBOX_CMD_SAVE_DFE_CTLE_CTX,
+	MBOX_CMD_AIRPLANE_MODE_ENABLED, /* unused */
+	MBOX_CMD_AIRPLANE_MODE_DISABLED, /* unused */
+	MBOX_CMD_START_HSIC_IDLE,
+	MBOX_CMD_STOP_HSIC_IDLE,
+	MBOX_CMD_DBC_WAKE_STACK, /* unused */
+	MBOX_CMD_HSIC_PRETEND_CONNECT,
+	MBOX_CMD_RESET_SSPI,
+	MBOX_CMD_DISABLE_SS_LFPS_DETECTION,
+	MBOX_CMD_ENABLE_SS_LFPS_DETECTION,
+
+	MBOX_CMD_MAX,
+
+	/* Response message to above commands */
+	MBOX_CMD_ACK = 128,
+	MBOX_CMD_NAK
+};
+
+static const char * const mbox_cmd_name[] = {
+	[  1] = "MSG_ENABLE",
+	[  2] = "INC_FALCON_CLOCK",
+	[  3] = "DEC_FALCON_CLOCK",
+	[  4] = "INC_SSPI_CLOCK",
+	[  5] = "DEC_SSPI_CLOCK",
+	[  6] = "SET_BW",
+	[  7] = "SET_SS_PWR_GATING",
+	[  8] = "SET_SS_PWR_UNGATING",
+	[  9] = "SAVE_DFE_CTLE_CTX",
+	[ 10] = "AIRPLANE_MODE_ENABLED",
+	[ 11] = "AIRPLANE_MODE_DISABLED",
+	[ 12] = "START_HSIC_IDLE",
+	[ 13] = "STOP_HSIC_IDLE",
+	[ 14] = "DBC_WAKE_STACK",
+	[ 15] = "HSIC_PRETEND_CONNECT",
+	[ 16] = "RESET_SSPI",
+	[ 17] = "DISABLE_SS_LFPS_DETECTION",
+	[ 18] = "ENABLE_SS_LFPS_DETECTION",
+	[128] = "ACK",
+	[129] = "NAK",
+};
+
+struct tegra_xusb_mbox_msg {
+	u32 cmd;
+	u32 data;
+};
+
+static inline u32 tegra_xusb_mbox_pack(const struct tegra_xusb_mbox_msg *msg)
+{
+	return (msg->cmd & CMD_TYPE_MASK) << CMD_TYPE_SHIFT |
+	       (msg->data & CMD_DATA_MASK) << CMD_DATA_SHIFT;
+}
+static inline void tegra_xusb_mbox_unpack(struct tegra_xusb_mbox_msg *msg,
+					  u32 value)
+{
+	msg->cmd = (value >> CMD_TYPE_SHIFT) & CMD_TYPE_MASK;
+	msg->data = (value >> CMD_DATA_SHIFT) & CMD_DATA_MASK;
+}
+
+static bool tegra_xusb_mbox_cmd_requires_ack(enum tegra_xusb_mbox_cmd cmd)
+{
+	switch (cmd) {
+	case MBOX_CMD_SET_BW:
+	case MBOX_CMD_ACK:
+	case MBOX_CMD_NAK:
+		return false;
+
+	default:
+		return true;
+	}
+}
+
+static int tegra_xusb_mbox_send(struct tegra_xusb *tegra,
+				const struct tegra_xusb_mbox_msg *msg)
+{
+	bool wait_for_idle = false;
+	u32 value;
+
+	/*
+	 * Acquire the mailbox. The firmware still owns the mailbox for
+	 * ACK/NAK messages.
+	 */
+	if (!(msg->cmd == MBOX_CMD_ACK || msg->cmd == MBOX_CMD_NAK)) {
+		value = fpci_readl(tegra, XUSB_CFG_ARU_MBOX_OWNER);
+		if (value != MBOX_OWNER_NONE) {
+			dev_err(tegra->dev, "mailbox is busy\n");
+			return -EBUSY;
+		}
+
+		fpci_writel(tegra, MBOX_OWNER_SW, XUSB_CFG_ARU_MBOX_OWNER);
+
+		value = fpci_readl(tegra, XUSB_CFG_ARU_MBOX_OWNER);
+		if (value != MBOX_OWNER_SW) {
+			dev_err(tegra->dev, "failed to acquire mailbox\n");
+			return -EBUSY;
+		}
+
+		wait_for_idle = true;
+	}
+
+	value = tegra_xusb_mbox_pack(msg);
+	fpci_writel(tegra, value, XUSB_CFG_ARU_MBOX_DATA_IN);
+
+	value = fpci_readl(tegra, XUSB_CFG_ARU_MBOX_CMD);
+	value |= MBOX_INT_EN | MBOX_DEST_FALC;
+	fpci_writel(tegra, value, XUSB_CFG_ARU_MBOX_CMD);
+
+	if (wait_for_idle) {
+		unsigned long timeout = jiffies + msecs_to_jiffies(250);
+
+		while (time_before(jiffies, timeout)) {
+			value = fpci_readl(tegra, XUSB_CFG_ARU_MBOX_OWNER);
+			if (value == MBOX_OWNER_NONE)
+				break;
+
+			usleep_range(10, 20);
+		}
+
+		if (time_after(jiffies, timeout))
+			value = fpci_readl(tegra, XUSB_CFG_ARU_MBOX_OWNER);
+
+		if (value != MBOX_OWNER_NONE)
+			return -ETIMEDOUT;
+	}
+
+	return 0;
+}
+
+static irqreturn_t tegra_xusb_mbox_irq(int irq, void *data)
+{
+	struct tegra_xusb *tegra = data;
+	u32 value;
+
+	/* clear mailbox interrupts */
+	value = fpci_readl(tegra, XUSB_CFG_ARU_SMI_INTR);
+	fpci_writel(tegra, value, XUSB_CFG_ARU_SMI_INTR);
+
+	if (value & MBOX_SMI_INTR_FW_HANG)
+		dev_err(tegra->dev, "controller firmware hang\n");
+
+	return IRQ_WAKE_THREAD;
+}
+
+static void tegra_xusb_mbox_handle(struct tegra_xusb *tegra,
+				   const struct tegra_xusb_mbox_msg *msg)
+{
+	struct tegra_xusb_padctl *padctl = tegra->padctl;
+	const struct tegra_xusb_soc *soc = tegra->soc;
+	struct device *dev = tegra->dev;
+	struct tegra_xusb_mbox_msg rsp;
+	unsigned long mask;
+	unsigned int port;
+	bool idle, enable;
+	int err;
+
+	memset(&rsp, 0, sizeof(rsp));
+
+	switch (msg->cmd) {
+	case MBOX_CMD_INC_FALC_CLOCK:
+	case MBOX_CMD_DEC_FALC_CLOCK:
+		rsp.data = clk_get_rate(tegra->falcon_clk) / 1000;
+		if (rsp.data != msg->data)
+			rsp.cmd = MBOX_CMD_NAK;
+		else
+			rsp.cmd = MBOX_CMD_ACK;
+
+		break;
+
+	case MBOX_CMD_INC_SSPI_CLOCK:
+	case MBOX_CMD_DEC_SSPI_CLOCK:
+		if (tegra->soc->scale_ss_clock) {
+			err = tegra_xusb_set_ss_clk(tegra, msg->data * 1000);
+			if (err < 0)
+				rsp.cmd = MBOX_CMD_NAK;
+			else
+				rsp.cmd = MBOX_CMD_ACK;
+
+			rsp.data = clk_get_rate(tegra->ss_src_clk) / 1000;
+		} else {
+			rsp.cmd = MBOX_CMD_ACK;
+			rsp.data = msg->data;
+		}
+
+		break;
+
+	case MBOX_CMD_SET_BW:
+		/*
+		 * TODO: Request bandwidth once EMC scaling is supported.
+		 * Ignore for now since ACK/NAK is not required for SET_BW
+		 * messages.
+		 */
+		break;
+
+	case MBOX_CMD_SAVE_DFE_CTLE_CTX:
+		err = tegra_xusb_padctl_usb3_save_context(padctl, msg->data);
+		if (err < 0) {
+			dev_err(dev, "failed to save context for USB3#%u: %d\n",
+				msg->data, err);
+			rsp.cmd = MBOX_CMD_NAK;
+		} else {
+			rsp.cmd = MBOX_CMD_ACK;
+		}
+
+		rsp.data = msg->data;
+		break;
+
+	case MBOX_CMD_START_HSIC_IDLE:
+	case MBOX_CMD_STOP_HSIC_IDLE:
+		if (msg->cmd == MBOX_CMD_STOP_HSIC_IDLE)
+			idle = false;
+		else
+			idle = true;
+
+		mask = extract_field(msg->data, 1 + soc->ports.hsic.offset,
+				     soc->ports.hsic.count);
+
+		for_each_set_bit(port, &mask, 32) {
+			err = tegra_xusb_padctl_hsic_set_idle(padctl, port,
+							      idle);
+			if (err < 0)
+				break;
+		}
+
+		if (err < 0) {
+			dev_err(dev, "failed to set HSIC#%u %s: %d\n", port,
+				idle ? "idle" : "busy", err);
+			rsp.cmd = MBOX_CMD_NAK;
+		} else {
+			rsp.cmd = MBOX_CMD_ACK;
+		}
+
+		rsp.data = msg->data;
+		break;
+
+	case MBOX_CMD_DISABLE_SS_LFPS_DETECTION:
+	case MBOX_CMD_ENABLE_SS_LFPS_DETECTION:
+		if (msg->cmd == MBOX_CMD_DISABLE_SS_LFPS_DETECTION)
+			enable = false;
+		else
+			enable = true;
+
+		mask = extract_field(msg->data, 1 + soc->ports.usb3.offset,
+				     soc->ports.usb3.count);
+
+		for_each_set_bit(port, &mask, soc->ports.usb3.count) {
+			err = tegra_xusb_padctl_usb3_set_lfps_detect(padctl,
+								     port,
+								     enable);
+			if (err < 0)
+				break;
+		}
+
+		if (err < 0) {
+			dev_err(dev,
+				"failed to %s LFPS detection on USB3#%u: %d\n",
+				enable ? "enable" : "disable", port, err);
+			rsp.cmd = MBOX_CMD_NAK;
+		} else {
+			rsp.cmd = MBOX_CMD_ACK;
+		}
+
+		rsp.data = msg->data;
+		break;
+
+	default:
+		dev_warn(dev, "unknown message: %#x\n", msg->cmd);
+		break;
+	}
+
+	if (rsp.cmd) {
+		const char *cmd = (rsp.cmd == MBOX_CMD_ACK) ? "ACK" : "NAK";
+
+		err = tegra_xusb_mbox_send(tegra, &rsp);
+		if (err < 0)
+			dev_err(dev, "failed to send %s: %d\n", cmd, err);
+	}
+}
+
+static irqreturn_t tegra_xusb_mbox_thread(int irq, void *data)
+{
+	struct tegra_xusb *tegra = data;
+	struct tegra_xusb_mbox_msg msg;
+	u32 value;
+
+	mutex_lock(&tegra->lock);
+
+	value = fpci_readl(tegra, XUSB_CFG_ARU_MBOX_DATA_OUT);
+	tegra_xusb_mbox_unpack(&msg, value);
+
+	value = fpci_readl(tegra, XUSB_CFG_ARU_MBOX_CMD);
+	value &= ~MBOX_DEST_SMI;
+	fpci_writel(tegra, value, XUSB_CFG_ARU_MBOX_CMD);
+
+	/* clear mailbox owner if no ACK/NAK is required */
+	if (!tegra_xusb_mbox_cmd_requires_ack(msg.cmd))
+		fpci_writel(tegra, MBOX_OWNER_NONE, XUSB_CFG_ARU_MBOX_OWNER);
+
+	tegra_xusb_mbox_handle(tegra, &msg);
+
+	mutex_unlock(&tegra->lock);
+	return IRQ_HANDLED;
+}
+
+static void tegra_xusb_ipfs_config(struct tegra_xusb *tegra,
+				   struct resource *regs)
+{
+	u32 value;
+
+	value = ipfs_readl(tegra, IPFS_XUSB_HOST_CONFIGURATION_0);
+	value |= IPFS_EN_FPCI;
+	ipfs_writel(tegra, value, IPFS_XUSB_HOST_CONFIGURATION_0);
+
+	usleep_range(10, 20);
+
+	/* Program BAR0 space */
+	value = fpci_readl(tegra, XUSB_CFG_4);
+	value &= ~(XUSB_BASE_ADDR_MASK << XUSB_BASE_ADDR_SHIFT);
+	value |= regs->start & (XUSB_BASE_ADDR_MASK << XUSB_BASE_ADDR_SHIFT);
+	fpci_writel(tegra, value, XUSB_CFG_4);
+
+	usleep_range(100, 200);
+
+	/* Enable bus master */
+	value = fpci_readl(tegra, XUSB_CFG_1);
+	value |= XUSB_IO_SPACE_EN | XUSB_MEM_SPACE_EN | XUSB_BUS_MASTER_EN;
+	fpci_writel(tegra, value, XUSB_CFG_1);
+
+	/* Enable interrupt assertion */
+	value = ipfs_readl(tegra, IPFS_XUSB_HOST_INTR_MASK_0);
+	value |= IPFS_IP_INT_MASK;
+	ipfs_writel(tegra, value, IPFS_XUSB_HOST_INTR_MASK_0);
+
+	/* Set hysteresis */
+	ipfs_writel(tegra, 0x80, IPFS_XUSB_HOST_CLKGATE_HYSTERESIS_0);
+}
+
+static int tegra_xusb_clk_enable(struct tegra_xusb *tegra)
+{
+	int err;
+
+	err = clk_prepare_enable(tegra->pll_e);
+	if (err < 0)
+		return err;
+
+	err = clk_prepare_enable(tegra->host_clk);
+	if (err < 0)
+		goto disable_plle;
+
+	err = clk_prepare_enable(tegra->ss_clk);
+	if (err < 0)
+		goto disable_host;
+
+	err = clk_prepare_enable(tegra->falcon_clk);
+	if (err < 0)
+		goto disable_ss;
+
+	err = clk_prepare_enable(tegra->fs_src_clk);
+	if (err < 0)
+		goto disable_falc;
+
+	err = clk_prepare_enable(tegra->hs_src_clk);
+	if (err < 0)
+		goto disable_fs_src;
+
+	if (tegra->soc->scale_ss_clock) {
+		err = tegra_xusb_set_ss_clk(tegra, TEGRA_XHCI_SS_HIGH_SPEED);
+		if (err < 0)
+			goto disable_hs_src;
+	}
+
+	return 0;
+
+disable_hs_src:
+	clk_disable_unprepare(tegra->hs_src_clk);
+disable_fs_src:
+	clk_disable_unprepare(tegra->fs_src_clk);
+disable_falc:
+	clk_disable_unprepare(tegra->falcon_clk);
+disable_ss:
+	clk_disable_unprepare(tegra->ss_clk);
+disable_host:
+	clk_disable_unprepare(tegra->host_clk);
+disable_plle:
+	clk_disable_unprepare(tegra->pll_e);
+	return err;
+}
+
+static void tegra_xusb_clk_disable(struct tegra_xusb *tegra)
+{
+	clk_disable_unprepare(tegra->pll_e);
+	clk_disable_unprepare(tegra->host_clk);
+	clk_disable_unprepare(tegra->ss_clk);
+	clk_disable_unprepare(tegra->falcon_clk);
+	clk_disable_unprepare(tegra->fs_src_clk);
+	clk_disable_unprepare(tegra->hs_src_clk);
+}
+
+static int tegra_xusb_phy_enable(struct tegra_xusb *tegra)
+{
+	unsigned int i;
+	int err;
+
+	for (i = 0; i < tegra->num_phys; i++) {
+		err = phy_init(tegra->phys[i]);
+		if (err)
+			goto disable_phy;
+
+		err = phy_power_on(tegra->phys[i]);
+		if (err) {
+			phy_exit(tegra->phys[i]);
+			goto disable_phy;
+		}
+	}
+
+	return 0;
+
+disable_phy:
+	while (i--) {
+		phy_power_off(tegra->phys[i]);
+		phy_exit(tegra->phys[i]);
+	}
+
+	return err;
+}
+
+static void tegra_xusb_phy_disable(struct tegra_xusb *tegra)
+{
+	unsigned int i;
+
+	for (i = 0; i < tegra->num_phys; i++) {
+		phy_power_off(tegra->phys[i]);
+		phy_exit(tegra->phys[i]);
+	}
+}
+
+static int tegra_xusb_load_firmware(struct tegra_xusb *tegra)
+{
+	unsigned int code_tag_blocks, code_size_blocks, code_blocks;
+	struct tegra_xusb_fw_header *header;
+	struct device *dev = tegra->dev;
+	const struct firmware *fw;
+	unsigned long timeout;
+	time_t timestamp;
+	struct tm time;
+	u64 address;
+	u32 value;
+	int err;
+
+	err = request_firmware(&fw, tegra->soc->firmware, tegra->dev);
+	if (err < 0) {
+		dev_err(tegra->dev, "failed to request firmware: %d\n", err);
+		return err;
+	}
+
+	/* Load Falcon controller with its firmware. */
+	header = (struct tegra_xusb_fw_header *)fw->data;
+	tegra->fw.size = le32_to_cpu(header->fwimg_len);
+
+	tegra->fw.virt = dma_alloc_coherent(tegra->dev, tegra->fw.size,
+					    &tegra->fw.phys, GFP_KERNEL);
+	if (!tegra->fw.virt) {
+		dev_err(tegra->dev, "failed to allocate memory for firmware\n");
+		release_firmware(fw);
+		return -ENOMEM;
+	}
+
+	header = (struct tegra_xusb_fw_header *)tegra->fw.virt;
+	memcpy(tegra->fw.virt, fw->data, tegra->fw.size);
+	release_firmware(fw);
+
+	if (csb_readl(tegra, XUSB_CSB_MP_ILOAD_BASE_LO) != 0) {
+		dev_info(dev, "Firmware already loaded, Falcon state %#x\n",
+			 csb_readl(tegra, XUSB_FALC_CPUCTL));
+		return 0;
+	}
+
+	/* Program the size of DFI into ILOAD_ATTR. */
+	csb_writel(tegra, tegra->fw.size, XUSB_CSB_MP_ILOAD_ATTR);
+
+	/*
+	 * Boot code of the firmware reads the ILOAD_BASE registers
+	 * to get to the start of the DFI in system memory.
+	 */
+	address = tegra->fw.phys + sizeof(*header);
+	csb_writel(tegra, address >> 32, XUSB_CSB_MP_ILOAD_BASE_HI);
+	csb_writel(tegra, address, XUSB_CSB_MP_ILOAD_BASE_LO);
+
+	/* Set BOOTPATH to 1 in APMAP. */
+	csb_writel(tegra, APMAP_BOOTPATH, XUSB_CSB_MP_APMAP);
+
+	/* Invalidate L2IMEM. */
+	csb_writel(tegra, L2IMEMOP_INVALIDATE_ALL, XUSB_CSB_MP_L2IMEMOP_TRIG);
+
+	/*
+	 * Initiate fetch of bootcode from system memory into L2IMEM.
+	 * Program bootcode location and size in system memory.
+	 */
+	code_tag_blocks = DIV_ROUND_UP(le32_to_cpu(header->boot_codetag),
+				       IMEM_BLOCK_SIZE);
+	code_size_blocks = DIV_ROUND_UP(le32_to_cpu(header->boot_codesize),
+					IMEM_BLOCK_SIZE);
+	code_blocks = code_tag_blocks + code_size_blocks;
+
+	value = ((code_tag_blocks & L2IMEMOP_SIZE_SRC_OFFSET_MASK) <<
+			L2IMEMOP_SIZE_SRC_OFFSET_SHIFT) |
+		((code_size_blocks & L2IMEMOP_SIZE_SRC_COUNT_MASK) <<
+			L2IMEMOP_SIZE_SRC_COUNT_SHIFT);
+	csb_writel(tegra, value, XUSB_CSB_MP_L2IMEMOP_SIZE);
+
+	/* Trigger L2IMEM load operation. */
+	csb_writel(tegra, L2IMEMOP_LOAD_LOCKED_RESULT,
+		   XUSB_CSB_MP_L2IMEMOP_TRIG);
+
+	/* Setup Falcon auto-fill. */
+	csb_writel(tegra, code_size_blocks, XUSB_FALC_IMFILLCTL);
+
+	value = ((code_tag_blocks & IMFILLRNG1_TAG_MASK) <<
+			IMFILLRNG1_TAG_LO_SHIFT) |
+		((code_blocks & IMFILLRNG1_TAG_MASK) <<
+			IMFILLRNG1_TAG_HI_SHIFT);
+	csb_writel(tegra, value, XUSB_FALC_IMFILLRNG1);
+
+	csb_writel(tegra, 0, XUSB_FALC_DMACTL);
+
+	msleep(50);
+
+	csb_writel(tegra, le32_to_cpu(header->boot_codetag),
+		   XUSB_FALC_BOOTVEC);
+
+	/* Boot Falcon CPU and wait for it to enter the STOPPED (idle) state. */
+	timeout = jiffies + msecs_to_jiffies(5);
+
+	csb_writel(tegra, CPUCTL_STARTCPU, XUSB_FALC_CPUCTL);
+
+	while (time_before(jiffies, timeout)) {
+		if (csb_readl(tegra, XUSB_FALC_CPUCTL) == CPUCTL_STATE_STOPPED)
+			break;
+
+		usleep_range(100, 200);
+	}
+
+	if (csb_readl(tegra, XUSB_FALC_CPUCTL) != CPUCTL_STATE_STOPPED) {
+		dev_err(dev, "Falcon failed to start, state: %#x\n",
+			csb_readl(tegra, XUSB_FALC_CPUCTL));
+		return -EIO;
+	}
+
+	timestamp = le32_to_cpu(header->fwimg_created_time);
+	time_to_tm(timestamp, 0, &time);
+
+	dev_info(dev, "Firmware timestamp: %ld-%02d-%02d %02d:%02d:%02d UTC\n",
+		 time.tm_year + 1900, time.tm_mon + 1, time.tm_mday,
+		 time.tm_hour, time.tm_min, time.tm_sec);
+
+	return 0;
+}
+
+static int tegra_xusb_probe(struct platform_device *pdev)
+{
+	struct tegra_xusb_mbox_msg msg;
+	struct resource *res, *regs;
+	struct tegra_xusb *tegra;
+	struct xhci_hcd *xhci;
+	unsigned int i, j, k;
+	struct phy *phy;
+	int err;
+
+	BUILD_BUG_ON(sizeof(struct tegra_xusb_fw_header) != 256);
+
+	tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL);
+	if (!tegra)
+		return -ENOMEM;
+
+	tegra->soc = of_device_get_match_data(&pdev->dev);
+	mutex_init(&tegra->lock);
+	tegra->dev = &pdev->dev;
+
+	regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	tegra->regs = devm_ioremap_resource(&pdev->dev, regs);
+	if (IS_ERR(tegra->regs))
+		return PTR_ERR(tegra->regs);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	tegra->fpci_base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(tegra->fpci_base))
+		return PTR_ERR(tegra->fpci_base);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+	tegra->ipfs_base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(tegra->ipfs_base))
+		return PTR_ERR(tegra->ipfs_base);
+
+	tegra->xhci_irq = platform_get_irq(pdev, 0);
+	if (tegra->xhci_irq < 0)
+		return tegra->xhci_irq;
+
+	tegra->mbox_irq = platform_get_irq(pdev, 1);
+	if (tegra->mbox_irq < 0)
+		return tegra->mbox_irq;
+
+	tegra->padctl = tegra_xusb_padctl_get(&pdev->dev);
+	if (IS_ERR(tegra->padctl))
+		return PTR_ERR(tegra->padctl);
+
+	tegra->host_rst = devm_reset_control_get(&pdev->dev, "xusb_host");
+	if (IS_ERR(tegra->host_rst)) {
+		err = PTR_ERR(tegra->host_rst);
+		dev_err(&pdev->dev, "failed to get xusb_host reset: %d\n", err);
+		goto put_padctl;
+	}
+
+	tegra->ss_rst = devm_reset_control_get(&pdev->dev, "xusb_ss");
+	if (IS_ERR(tegra->ss_rst)) {
+		err = PTR_ERR(tegra->ss_rst);
+		dev_err(&pdev->dev, "failed to get xusb_ss reset: %d\n", err);
+		goto put_padctl;
+	}
+
+	tegra->host_clk = devm_clk_get(&pdev->dev, "xusb_host");
+	if (IS_ERR(tegra->host_clk)) {
+		err = PTR_ERR(tegra->host_clk);
+		dev_err(&pdev->dev, "failed to get xusb_host: %d\n", err);
+		goto put_padctl;
+	}
+
+	tegra->falcon_clk = devm_clk_get(&pdev->dev, "xusb_falcon_src");
+	if (IS_ERR(tegra->falcon_clk)) {
+		err = PTR_ERR(tegra->falcon_clk);
+		dev_err(&pdev->dev, "failed to get xusb_falcon_src: %d\n", err);
+		goto put_padctl;
+	}
+
+	tegra->ss_clk = devm_clk_get(&pdev->dev, "xusb_ss");
+	if (IS_ERR(tegra->ss_clk)) {
+		err = PTR_ERR(tegra->ss_clk);
+		dev_err(&pdev->dev, "failed to get xusb_ss: %d\n", err);
+		goto put_padctl;
+	}
+
+	tegra->ss_src_clk = devm_clk_get(&pdev->dev, "xusb_ss_src");
+	if (IS_ERR(tegra->ss_src_clk)) {
+		err = PTR_ERR(tegra->ss_src_clk);
+		dev_err(&pdev->dev, "failed to get xusb_ss_src: %d\n", err);
+		goto put_padctl;
+	}
+
+	tegra->hs_src_clk = devm_clk_get(&pdev->dev, "xusb_hs_src");
+	if (IS_ERR(tegra->hs_src_clk)) {
+		err = PTR_ERR(tegra->hs_src_clk);
+		dev_err(&pdev->dev, "failed to get xusb_hs_src: %d\n", err);
+		goto put_padctl;
+	}
+
+	tegra->fs_src_clk = devm_clk_get(&pdev->dev, "xusb_fs_src");
+	if (IS_ERR(tegra->fs_src_clk)) {
+		err = PTR_ERR(tegra->fs_src_clk);
+		dev_err(&pdev->dev, "failed to get xusb_fs_src: %d\n", err);
+		goto put_padctl;
+	}
+
+	tegra->pll_u_480m = devm_clk_get(&pdev->dev, "pll_u_480m");
+	if (IS_ERR(tegra->pll_u_480m)) {
+		err = PTR_ERR(tegra->pll_u_480m);
+		dev_err(&pdev->dev, "failed to get pll_u_480m: %d\n", err);
+		goto put_padctl;
+	}
+
+	tegra->clk_m = devm_clk_get(&pdev->dev, "clk_m");
+	if (IS_ERR(tegra->clk_m)) {
+		err = PTR_ERR(tegra->clk_m);
+		dev_err(&pdev->dev, "failed to get clk_m: %d\n", err);
+		goto put_padctl;
+	}
+
+	tegra->pll_e = devm_clk_get(&pdev->dev, "pll_e");
+	if (IS_ERR(tegra->pll_e)) {
+		err = PTR_ERR(tegra->pll_e);
+		dev_err(&pdev->dev, "failed to get pll_e: %d\n", err);
+		goto put_padctl;
+	}
+
+	tegra->supplies = devm_kcalloc(&pdev->dev, tegra->soc->num_supplies,
+				       sizeof(*tegra->supplies), GFP_KERNEL);
+	if (!tegra->supplies) {
+		err = -ENOMEM;
+		goto put_padctl;
+	}
+
+	for (i = 0; i < tegra->soc->num_supplies; i++)
+		tegra->supplies[i].supply = tegra->soc->supply_names[i];
+
+	err = devm_regulator_bulk_get(&pdev->dev, tegra->soc->num_supplies,
+				      tegra->supplies);
+	if (err) {
+		dev_err(&pdev->dev, "failed to get regulators: %d\n", err);
+		goto put_padctl;
+	}
+
+	for (i = 0; i < tegra->soc->num_types; i++)
+		tegra->num_phys += tegra->soc->phy_types[i].num;
+
+	tegra->phys = devm_kcalloc(&pdev->dev, tegra->num_phys,
+				   sizeof(*tegra->phys), GFP_KERNEL);
+	if (!tegra->phys) {
+		dev_err(&pdev->dev, "failed to allocate PHY array\n");
+		err = -ENOMEM;
+		goto put_padctl;
+	}
+
+	for (i = 0, k = 0; i < tegra->soc->num_types; i++) {
+		char prop[8];
+
+		for (j = 0; j < tegra->soc->phy_types[i].num; j++) {
+			snprintf(prop, sizeof(prop), "%s-%d",
+				 tegra->soc->phy_types[i].name, j);
+
+			phy = devm_phy_optional_get(&pdev->dev, prop);
+			if (IS_ERR(phy)) {
+				dev_err(&pdev->dev,
+					"failed to get PHY %s: %ld\n", prop,
+					PTR_ERR(phy));
+				err = PTR_ERR(phy);
+				goto put_padctl;
+			}
+
+			tegra->phys[k++] = phy;
+		}
+	}
+
+	err = tegra_xusb_clk_enable(tegra);
+	if (err) {
+		dev_err(&pdev->dev, "failed to enable clocks: %d\n", err);
+		goto put_padctl;
+	}
+
+	err = regulator_bulk_enable(tegra->soc->num_supplies, tegra->supplies);
+	if (err) {
+		dev_err(&pdev->dev, "failed to enable regulators: %d\n", err);
+		goto disable_clk;
+	}
+
+	err = tegra_xusb_phy_enable(tegra);
+	if (err < 0) {
+		dev_err(&pdev->dev, "failed to enable PHYs: %d\n", err);
+		goto disable_regulator;
+	}
+
+	tegra_xusb_ipfs_config(tegra, regs);
+
+	err = tegra_xusb_load_firmware(tegra);
+	if (err < 0) {
+		dev_err(&pdev->dev, "failed to load firmware: %d\n", err);
+		goto disable_phy;
+	}
+
+	tegra->hcd = usb_create_hcd(&tegra_xhci_hc_driver, &pdev->dev,
+				    dev_name(&pdev->dev));
+	if (!tegra->hcd) {
+		err = -ENOMEM;
+		goto disable_phy;
+	}
+
+	/*
+	 * This must happen after usb_create_hcd(), because usb_create_hcd()
+	 * will overwrite the drvdata of the device with the hcd it creates.
+	 */
+	platform_set_drvdata(pdev, tegra);
+
+	tegra->hcd->regs = tegra->regs;
+	tegra->hcd->rsrc_start = regs->start;
+	tegra->hcd->rsrc_len = resource_size(regs);
+
+	err = usb_add_hcd(tegra->hcd, tegra->xhci_irq, IRQF_SHARED);
+	if (err < 0) {
+		dev_err(&pdev->dev, "failed to add USB HCD: %d\n", err);
+		goto put_usb2;
+	}
+
+	device_wakeup_enable(tegra->hcd->self.controller);
+
+	xhci = hcd_to_xhci(tegra->hcd);
+
+	xhci->shared_hcd = usb_create_shared_hcd(&tegra_xhci_hc_driver,
+						 &pdev->dev,
+						 dev_name(&pdev->dev),
+						 tegra->hcd);
+	if (!xhci->shared_hcd) {
+		dev_err(&pdev->dev, "failed to create shared HCD\n");
+		goto remove_usb2;
+	}
+
+	err = usb_add_hcd(xhci->shared_hcd, tegra->xhci_irq, IRQF_SHARED);
+	if (err < 0) {
+		dev_err(&pdev->dev, "failed to add shared HCD: %d\n", err);
+		goto put_usb3;
+	}
+
+	mutex_lock(&tegra->lock);
+
+	/* Enable firmware messages from controller. */
+	msg.cmd = MBOX_CMD_MSG_ENABLED;
+	msg.data = 0;
+
+	err = tegra_xusb_mbox_send(tegra, &msg);
+	if (err < 0) {
+		dev_err(&pdev->dev, "failed to enable messages: %d\n", err);
+		mutex_unlock(&tegra->lock);
+		goto remove_usb3;
+	}
+
+	mutex_unlock(&tegra->lock);
+
+	err = devm_request_threaded_irq(&pdev->dev, tegra->mbox_irq,
+					tegra_xusb_mbox_irq,
+					tegra_xusb_mbox_thread, 0,
+					dev_name(&pdev->dev), tegra);
+	if (err < 0) {
+		dev_err(&pdev->dev, "failed to request IRQ: %d\n", err);
+		goto remove_usb3;
+	}
+
+	return 0;
+
+remove_usb3:
+	usb_remove_hcd(xhci->shared_hcd);
+put_usb3:
+	usb_put_hcd(xhci->shared_hcd);
+remove_usb2:
+	usb_remove_hcd(tegra->hcd);
+put_usb2:
+	usb_put_hcd(tegra->hcd);
+disable_phy:
+	tegra_xusb_phy_disable(tegra);
+disable_regulator:
+	regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies);
+disable_clk:
+	tegra_xusb_clk_disable(tegra);
+put_padctl:
+	tegra_xusb_padctl_put(tegra->padctl);
+	return err;
+}
+
+static int tegra_xusb_remove(struct platform_device *pdev)
+{
+	struct tegra_xusb *tegra = platform_get_drvdata(pdev);
+	struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd);
+
+	usb_remove_hcd(xhci->shared_hcd);
+	usb_put_hcd(xhci->shared_hcd);
+	usb_remove_hcd(tegra->hcd);
+	usb_put_hcd(tegra->hcd);
+
+	dma_free_coherent(&pdev->dev, tegra->fw.size, tegra->fw.virt,
+			  tegra->fw.phys);
+
+	tegra_xusb_phy_disable(tegra);
+	regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies);
+	tegra_xusb_clk_disable(tegra);
+
+	tegra_xusb_padctl_put(tegra->padctl);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int tegra_xusb_suspend(struct device *dev)
+{
+	struct tegra_xusb *tegra = dev_get_drvdata(dev);
+	struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd);
+	bool wakeup = device_may_wakeup(dev);
+
+	/* TODO: Powergate controller across suspend/resume. */
+	return xhci_suspend(xhci, wakeup);
+}
+
+static int tegra_xusb_resume(struct device *dev)
+{
+	struct tegra_xusb *tegra = dev_get_drvdata(dev);
+	struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd);
+
+	return xhci_resume(xhci, 0);
+}
+#endif
+
+static const struct dev_pm_ops tegra_xusb_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(tegra_xusb_suspend, tegra_xusb_resume)
+};
+
+static const char * const tegra124_supply_names[] = {
+	"avddio-pex",
+	"dvddio-pex",
+	"avdd-usb",
+	"avdd-pll-utmip",
+	"avdd-pll-erefe",
+	"avdd-usb-ss-pll",
+	"hvdd-usb-ss",
+	"hvdd-usb-ss-pll-e",
+};
+
+static const struct tegra_xusb_phy_type tegra124_phy_types[] = {
+	{ .name = "usb3", .num = 2, },
+	{ .name = "usb2", .num = 3, },
+	{ .name = "hsic", .num = 2, },
+};
+
+static const struct tegra_xusb_soc tegra124_soc = {
+	.firmware = "nvidia/tegra124/xusb.bin",
+	.supply_names = tegra124_supply_names,
+	.num_supplies = ARRAY_SIZE(tegra124_supply_names),
+	.phy_types = tegra124_phy_types,
+	.num_types = ARRAY_SIZE(tegra124_phy_types),
+	.ports = {
+		.usb2 = { .offset = 4, .count = 4, },
+		.hsic = { .offset = 6, .count = 2, },
+		.usb3 = { .offset = 0, .count = 2, },
+	},
+	.scale_ss_clock = true,
+};
+MODULE_FIRMWARE("nvidia/tegra124/xusb.bin");
+
+static const char * const tegra210_supply_names[] = {
+	"dvddio-pex",
+	"hvddio-pex",
+	"avdd-usb",
+	"avdd-pll-utmip",
+	"avdd-pll-uerefe",
+	"dvdd-pex-pll",
+	"hvdd-pex-pll-e",
+};
+
+static const struct tegra_xusb_phy_type tegra210_phy_types[] = {
+	{ .name = "usb3", .num = 4, },
+	{ .name = "usb2", .num = 4, },
+	{ .name = "hsic", .num = 1, },
+};
+
+static const struct tegra_xusb_soc tegra210_soc = {
+	.firmware = "nvidia/tegra210/xusb.bin",
+	.supply_names = tegra210_supply_names,
+	.num_supplies = ARRAY_SIZE(tegra210_supply_names),
+	.phy_types = tegra210_phy_types,
+	.num_types = ARRAY_SIZE(tegra210_phy_types),
+	.ports = {
+		.usb2 = { .offset = 4, .count = 4, },
+		.hsic = { .offset = 8, .count = 1, },
+		.usb3 = { .offset = 0, .count = 4, },
+	},
+	.scale_ss_clock = false,
+};
+MODULE_FIRMWARE("nvidia/tegra210/xusb.bin");
+
+static const struct of_device_id tegra_xusb_of_match[] = {
+	{ .compatible = "nvidia,tegra124-xusb", .data = &tegra124_soc },
+	{ .compatible = "nvidia,tegra210-xusb", .data = &tegra210_soc },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, tegra_xusb_of_match);
+
+static struct platform_driver tegra_xusb_driver = {
+	.probe = tegra_xusb_probe,
+	.remove = tegra_xusb_remove,
+	.driver = {
+		.name = "tegra-xusb",
+		.pm = &tegra_xusb_pm_ops,
+		.of_match_table = tegra_xusb_of_match,
+	},
+};
+
+static void tegra_xhci_quirks(struct device *dev, struct xhci_hcd *xhci)
+{
+	xhci->quirks |= XHCI_PLAT;
+}
+
+static int tegra_xhci_setup(struct usb_hcd *hcd)
+{
+	return xhci_gen_setup(hcd, tegra_xhci_quirks);
+}
+
+static const struct xhci_driver_overrides tegra_xhci_overrides __initconst = {
+	.extra_priv_size = sizeof(struct xhci_hcd),
+	.reset = tegra_xhci_setup,
+};
+
+static int __init tegra_xusb_init(void)
+{
+	xhci_init_driver(&tegra_xhci_hc_driver, &tegra_xhci_overrides);
+
+	return platform_driver_register(&tegra_xusb_driver);
+}
+module_init(tegra_xusb_init);
+
+static void __exit tegra_xusb_exit(void)
+{
+	platform_driver_unregister(&tegra_xusb_driver);
+}
+module_exit(tegra_xusb_exit);
+
+MODULE_AUTHOR("Andrew Bresticker <abrestic@chromium.org>");
+MODULE_DESCRIPTION("NVIDIA Tegra XUSB xHCI host-controller driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/clk/tegra.h b/include/linux/clk/tegra.h
index 57bf7aa..7007a5f 100644
--- a/include/linux/clk/tegra.h
+++ b/include/linux/clk/tegra.h
@@ -121,4 +121,9 @@
 }
 #endif
 
+extern void tegra210_xusb_pll_hw_control_enable(void);
+extern void tegra210_xusb_pll_hw_sequence_start(void);
+extern void tegra210_sata_pll_hw_control_enable(void);
+extern void tegra210_sata_pll_hw_sequence_start(void);
+
 #endif /* __LINUX_CLK_TEGRA_H_ */
diff --git a/include/linux/phy/phy.h b/include/linux/phy/phy.h
index 8cf05e3..a810f2a 100644
--- a/include/linux/phy/phy.h
+++ b/include/linux/phy/phy.h
@@ -77,6 +77,7 @@
  */
 struct phy_provider {
 	struct device		*dev;
+	struct device_node	*children;
 	struct module		*owner;
 	struct list_head	list;
 	struct phy * (*of_xlate)(struct device *dev,
@@ -93,10 +94,16 @@
 #define	to_phy(a)	(container_of((a), struct phy, dev))
 
 #define	of_phy_provider_register(dev, xlate)	\
-	__of_phy_provider_register((dev), THIS_MODULE, (xlate))
+	__of_phy_provider_register((dev), NULL, THIS_MODULE, (xlate))
 
 #define	devm_of_phy_provider_register(dev, xlate)	\
-	__devm_of_phy_provider_register((dev), THIS_MODULE, (xlate))
+	__devm_of_phy_provider_register((dev), NULL, THIS_MODULE, (xlate))
+
+#define of_phy_provider_register_full(dev, children, xlate) \
+	__of_phy_provider_register(dev, children, THIS_MODULE, xlate)
+
+#define devm_of_phy_provider_register_full(dev, children, xlate) \
+	__devm_of_phy_provider_register(dev, children, THIS_MODULE, xlate)
 
 static inline void phy_set_drvdata(struct phy *phy, void *data)
 {
@@ -147,11 +154,13 @@
 void phy_destroy(struct phy *phy);
 void devm_phy_destroy(struct device *dev, struct phy *phy);
 struct phy_provider *__of_phy_provider_register(struct device *dev,
-	struct module *owner, struct phy * (*of_xlate)(struct device *dev,
-	struct of_phandle_args *args));
+	struct device_node *children, struct module *owner,
+	struct phy * (*of_xlate)(struct device *dev,
+				 struct of_phandle_args *args));
 struct phy_provider *__devm_of_phy_provider_register(struct device *dev,
-	struct module *owner, struct phy * (*of_xlate)(struct device *dev,
-	struct of_phandle_args *args));
+	struct device_node *children, struct module *owner,
+	struct phy * (*of_xlate)(struct device *dev,
+				 struct of_phandle_args *args));
 void of_phy_provider_unregister(struct phy_provider *phy_provider);
 void devm_of_phy_provider_unregister(struct device *dev,
 	struct phy_provider *phy_provider);
@@ -312,15 +321,17 @@
 }
 
 static inline struct phy_provider *__of_phy_provider_register(
-	struct device *dev, struct module *owner, struct phy * (*of_xlate)(
-	struct device *dev, struct of_phandle_args *args))
+	struct device *dev, struct device_node *children, struct module *owner,
+	struct phy * (*of_xlate)(struct device *dev,
+				 struct of_phandle_args *args))
 {
 	return ERR_PTR(-ENOSYS);
 }
 
 static inline struct phy_provider *__devm_of_phy_provider_register(struct device
-	*dev, struct module *owner, struct phy * (*of_xlate)(struct device *dev,
-	struct of_phandle_args *args))
+	*dev, struct device_node *children, struct module *owner,
+	struct phy * (*of_xlate)(struct device *dev,
+				 struct of_phandle_args *args))
 {
 	return ERR_PTR(-ENOSYS);
 }
diff --git a/include/linux/phy/tegra/xusb.h b/include/linux/phy/tegra/xusb.h
new file mode 100644
index 0000000..8e1a57a
--- /dev/null
+++ b/include/linux/phy/tegra/xusb.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2016, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.
+ */
+
+#ifndef PHY_TEGRA_XUSB_H
+#define PHY_TEGRA_XUSB_H
+
+struct tegra_xusb_padctl;
+struct device;
+
+struct tegra_xusb_padctl *tegra_xusb_padctl_get(struct device *dev);
+void tegra_xusb_padctl_put(struct tegra_xusb_padctl *padctl);
+
+int tegra_xusb_padctl_usb3_save_context(struct tegra_xusb_padctl *padctl,
+					unsigned int port);
+int tegra_xusb_padctl_hsic_set_idle(struct tegra_xusb_padctl *padctl,
+				    unsigned int port, bool idle);
+int tegra_xusb_padctl_usb3_set_lfps_detect(struct tegra_xusb_padctl *padctl,
+					   unsigned int port, bool enable);
+
+#endif /* PHY_TEGRA_XUSB_H */
diff --git a/include/soc/tegra/fuse.h b/include/soc/tegra/fuse.h
index 961b821..b4c9219 100644
--- a/include/soc/tegra/fuse.h
+++ b/include/soc/tegra/fuse.h
@@ -26,6 +26,7 @@
 
 #define TEGRA_FUSE_SKU_CALIB_0	0xf0
 #define TEGRA30_FUSE_SATA_CALIB	0x124
+#define TEGRA_FUSE_USB_CALIB_EXT_0 0x250
 
 #ifndef __ASSEMBLY__