Browse Source

Merge tag 'usb-4.12-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb

Pull USB updates from Greg KH:
 "Here is the big USB patchset for 4.12-rc1.

  Lots of good stuff here, after many many many attempts, the kernel
  finally has a working typeC interface, many thanks to Heikki and
  Guenter and others who have taken the time to get this merged. It
  wasn't an easy path for them at all.

  There's also a staging driver that uses this new api, which is why
  it's coming in through this tree.

  Along with that, there's the usual huge number of changes for gadget
  drivers, xhci, and other stuff. Johan also finally refactored pretty
  much every driver that was looking at USB endpoints to do it in a
  common way, which will help prevent any "badly-formed" devices from
  causing problems in drivers. That too wasn't a simple task.

  All of these have been in linux-next for a while with no reported
  issues"

* tag 'usb-4.12-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (263 commits)
  staging: typec: Fairchild FUSB302 Type-c chip driver
  staging: typec: Type-C Port Controller Interface driver (tcpci)
  staging: typec: USB Type-C Port Manager (tcpm)
  usb: host: xhci: remove #ifdef around PM functions
  usb: musb: don't mark of_dev_auxdata as initdata
  usb: misc: legousbtower: Fix buffers on stack
  USB: Revert "cdc-wdm: fix "out-of-sync" due to missing notifications"
  usb: Make sure usb/phy/of gets built-in
  USB: storage: e-mail update in drivers/usb/storage/unusual_devs.h
  usb: host: xhci: print correct command ring address
  usb: host: xhci: delete sp_dma_buffers for scratchpad
  usb: host: xhci: using correct specification chapter reference for DCBAAP
  xhci: switch to pci_alloc_irq_vectors
  usb: host: xhci-plat: set resume_quirk() for R-Car controllers
  usb: host: xhci-plat: add resume_quirk()
  usb: host: xhci-plat: enable clk in resume timing
  usb: host: plat: Enable xHCI plat runtime PM
  USB: serial: ftdi_sio: add device ID for Microsemi/Arrow SF2PLUS Dev Kit
  USB: serial: constify static arrays
  usb: fix some references for /proc/bus/usb
  ...
Linus Torvalds 8 years ago
parent
commit
8f28472a73
100 changed files with 11002 additions and 1285 deletions
  1. 276 0
      Documentation/ABI/testing/sysfs-class-typec
  2. 9 0
      Documentation/ABI/testing/sysfs-platform-chipidea-usb2
  3. 15 0
      Documentation/ABI/testing/sysfs-platform-renesas_usb3
  4. 80 13
      Documentation/devicetree/bindings/phy/phy-mt65xx-usb.txt
  5. 6 0
      Documentation/devicetree/bindings/phy/phy-rockchip-inno-usb2.txt
  6. 106 0
      Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt
  7. 43 0
      Documentation/devicetree/bindings/phy/qcom-qusb2-phy.txt
  8. 1 0
      Documentation/devicetree/bindings/phy/rockchip-usb-phy.txt
  9. 1 0
      Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt
  10. 4 0
      Documentation/devicetree/bindings/soc/rockchip/grf.txt
  11. 3 1
      Documentation/devicetree/bindings/usb/ehci-orion.txt
  12. 1 0
      Documentation/devicetree/bindings/usb/generic.txt
  13. 2 2
      Documentation/media/v4l-drivers/philips.rst
  14. 184 0
      Documentation/usb/typec.rst
  15. 9 0
      MAINTAINERS
  16. 6 0
      arch/arm64/boot/dts/marvell/armada-3720-db.dts
  17. 7 0
      arch/arm64/boot/dts/marvell/armada-37xx.dtsi
  18. 1 0
      drivers/Makefile
  19. 19 0
      drivers/phy/Kconfig
  20. 2 0
      drivers/phy/Makefile
  21. 49 20
      drivers/phy/phy-bcm-ns-usb3.c
  22. 3 3
      drivers/phy/phy-exynos-dp-video.c
  23. 35 36
      drivers/phy/phy-exynos-mipi-video.c
  24. 2 6
      drivers/phy/phy-exynos-pcie.c
  25. 3 3
      drivers/phy/phy-exynos5-usbdrd.c
  26. 3 3
      drivers/phy/phy-meson8b-usb2.c
  27. 307 170
      drivers/phy/phy-mt65xx-usb3.c
  28. 1153 0
      drivers/phy/phy-qcom-qmp.c
  29. 493 0
      drivers/phy/phy-qcom-qusb2.c
  30. 24 7
      drivers/phy/phy-rcar-gen3-usb2.c
  31. 47 6
      drivers/phy/phy-rockchip-inno-usb2.c
  32. 19 0
      drivers/phy/phy-rockchip-usb.c
  33. 37 21
      drivers/phy/phy-sun4i-usb.c
  34. 2 0
      drivers/staging/Kconfig
  35. 1 1
      drivers/staging/Makefile
  36. 24 0
      drivers/staging/typec/Kconfig
  37. 3 0
      drivers/staging/typec/Makefile
  38. 15 0
      drivers/staging/typec/TODO
  39. 7 0
      drivers/staging/typec/fusb302/Kconfig
  40. 1 0
      drivers/staging/typec/fusb302/Makefile
  41. 6 0
      drivers/staging/typec/fusb302/TODO
  42. 1815 0
      drivers/staging/typec/fusb302/fusb302.c
  43. 186 0
      drivers/staging/typec/fusb302/fusb302_reg.h
  44. 281 0
      drivers/staging/typec/pd.h
  45. 31 0
      drivers/staging/typec/pd_bdo.h
  46. 249 0
      drivers/staging/typec/pd_vdo.h
  47. 526 0
      drivers/staging/typec/tcpci.c
  48. 133 0
      drivers/staging/typec/tcpci.h
  49. 3465 0
      drivers/staging/typec/tcpm.c
  50. 150 0
      drivers/staging/typec/tcpm.h
  51. 13 1
      drivers/usb/Kconfig
  52. 3 1
      drivers/usb/Makefile
  53. 1 1
      drivers/usb/atm/cxacru.c
  54. 1 1
      drivers/usb/chipidea/Kconfig
  55. 2 0
      drivers/usb/chipidea/ci.h
  56. 61 6
      drivers/usb/chipidea/core.c
  57. 2 1
      drivers/usb/chipidea/host.c
  58. 21 12
      drivers/usb/chipidea/udc.c
  59. 1 1
      drivers/usb/class/Kconfig
  60. 101 50
      drivers/usb/class/cdc-acm.c
  61. 3 1
      drivers/usb/class/cdc-acm.h
  62. 4 99
      drivers/usb/class/cdc-wdm.c
  63. 14 23
      drivers/usb/class/usblp.c
  64. 18 38
      drivers/usb/class/usbtmc.c
  65. 7 0
      drivers/usb/common/usb-otg-fsm.c
  66. 1 1
      drivers/usb/core/Makefile
  67. 6 6
      drivers/usb/core/buffer.c
  68. 2 2
      drivers/usb/core/devices.c
  69. 21 0
      drivers/usb/core/driver.c
  70. 7 2
      drivers/usb/core/file.c
  71. 46 36
      drivers/usb/core/hcd.c
  72. 10 1
      drivers/usb/core/hub.c
  73. 23 0
      drivers/usb/core/of.c
  74. 143 9
      drivers/usb/core/usb.c
  75. 1 1
      drivers/usb/dwc2/Kconfig
  76. 5 0
      drivers/usb/dwc2/core.h
  77. 15 1
      drivers/usb/dwc2/hcd.c
  78. 2 0
      drivers/usb/dwc2/hw.h
  79. 19 0
      drivers/usb/dwc2/params.c
  80. 4 14
      drivers/usb/dwc2/platform.c
  81. 2 1
      drivers/usb/dwc3/Kconfig
  82. 4 0
      drivers/usb/dwc3/Makefile
  83. 80 29
      drivers/usb/dwc3/core.c
  84. 136 125
      drivers/usb/dwc3/core.h
  85. 28 0
      drivers/usb/dwc3/debug.h
  86. 11 94
      drivers/usb/dwc3/debugfs.c
  87. 85 0
      drivers/usb/dwc3/drd.c
  88. 11 11
      drivers/usb/dwc3/dwc3-exynos.c
  89. 24 24
      drivers/usb/dwc3/dwc3-omap.c
  90. 66 85
      drivers/usb/dwc3/ep0.c
  91. 52 145
      drivers/usb/dwc3/gadget.c
  92. 10 10
      drivers/usb/dwc3/gadget.h
  93. 2 56
      drivers/usb/dwc3/trace.h
  94. 9 2
      drivers/usb/gadget/Kconfig
  95. 17 63
      drivers/usb/gadget/function/f_fs.c
  96. 12 12
      drivers/usb/gadget/function/u_ether.c
  97. 8 6
      drivers/usb/gadget/function/u_fs.h
  98. 8 8
      drivers/usb/gadget/function/uvc_configfs.c
  99. 9 8
      drivers/usb/gadget/legacy/inode.c
  100. 26 6
      drivers/usb/gadget/udc/Kconfig

+ 276 - 0
Documentation/ABI/testing/sysfs-class-typec

@@ -0,0 +1,276 @@
+USB Type-C port devices (eg. /sys/class/typec/port0/)
+
+What:		/sys/class/typec/<port>/data_role
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		The supported USB data roles. This attribute can be used for
+		requesting data role swapping on the port. Swapping is supported
+		as synchronous operation, so write(2) to the attribute will not
+		return until the operation has finished. The attribute is
+		notified about role changes so that poll(2) on the attribute
+		wakes up. Change on the role will also generate uevent
+		KOBJ_CHANGE on the port. The current role is show in brackets,
+		for example "[host] device" when DRP port is in host mode.
+
+		Valid values: host, device
+
+What:		/sys/class/typec/<port>/power_role
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		The supported power roles. This attribute can be used to request
+		power role swap on the port when the port supports USB Power
+		Delivery. Swapping is supported as synchronous operation, so
+		write(2) to the attribute will not return until the operation
+		has finished. The attribute is notified about role changes so
+		that poll(2) on the attribute wakes up. Change on the role will
+		also generate uevent KOBJ_CHANGE. The current role is show in
+		brackets, for example "[source] sink" when in source mode.
+
+		Valid values: source, sink
+
+What:		/sys/class/typec/<port>/vconn_source
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows is the port VCONN Source. This attribute can be used to
+		request VCONN swap to change the VCONN Source during connection
+		when both the port and the partner support USB Power Delivery.
+		Swapping is supported as synchronous operation, so write(2) to
+		the attribute will not return until the operation has finished.
+		The attribute is notified about VCONN source changes so that
+		poll(2) on the attribute wakes up. Change on VCONN source also
+		generates uevent KOBJ_CHANGE.
+
+		Valid values:
+		- "no" when the port is not the VCONN Source
+		- "yes" when the port is the VCONN Source
+
+What:		/sys/class/typec/<port>/power_operation_mode
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows the current power operational mode the port is in. The
+		power operation mode means current level for VBUS. In case USB
+		Power Delivery communication is used for negotiating the levels,
+		power operation mode should show "usb_power_delivery".
+
+		Valid values:
+		- default
+		- 1.5A
+		- 3.0A
+		- usb_power_delivery
+
+What:		/sys/class/typec/<port>/preferred_role
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		The user space can notify the driver about the preferred role.
+		It should be handled as enabling of Try.SRC or Try.SNK, as
+		defined in USB Type-C specification, in the port drivers. By
+		default the preferred role should come from the platform.
+
+		Valid values: source, sink, none (to remove preference)
+
+What:		/sys/class/typec/<port>/supported_accessory_modes
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Space separated list of accessory modes, defined in the USB
+		Type-C specification, the port supports.
+
+What:		/sys/class/typec/<port>/usb_power_delivery_revision
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Revision number of the supported USB Power Delivery
+		specification, or 0 when USB Power Delivery is not supported.
+
+What:		/sys/class/typec/<port>/usb_typec_revision
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Revision number of the supported USB Type-C specification.
+
+
+USB Type-C partner devices (eg. /sys/class/typec/port0-partner/)
+
+What:		/sys/class/typec/<port>-partner/accessory_mode
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows the Accessory Mode name when the partner is an Accessory.
+		The Accessory Modes are defined in USB Type-C Specification.
+
+What:		/sys/class/typec/<port>-partner/supports_usb_power_delivery
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows if the partner supports USB Power Delivery communication:
+		Valid values: yes, no
+
+What:		/sys/class/typec/<port>-partner>/identity/
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		This directory appears only if the port device driver is capable
+		of showing the result of Discover Identity USB power delivery
+		command. That will not always be possible even when USB power
+		delivery is supported, for example when USB power delivery
+		communication for the port is mostly handled in firmware. If the
+		directory exists, it will have an attribute file for every VDO
+		in Discover Identity command result.
+
+What:		/sys/class/typec/<port>-partner/identity/id_header
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		ID Header VDO part of Discover Identity command result. The
+		value will show 0 until Discover Identity command result becomes
+		available. The value can be polled.
+
+What:		/sys/class/typec/<port>-partner/identity/cert_stat
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Cert Stat VDO part of Discover Identity command result. The
+		value will show 0 until Discover Identity command result becomes
+		available. The value can be polled.
+
+What:		/sys/class/typec/<port>-partner/identity/product
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Product VDO part of Discover Identity command result. The value
+		will show 0 until Discover Identity command result becomes
+		available. The value can be polled.
+
+
+USB Type-C cable devices (eg. /sys/class/typec/port0-cable/)
+
+Note: Electronically Marked Cables will have a device also for one cable plug
+(eg. /sys/class/typec/port0-plug0). If the cable is active and has also SOP
+Double Prime controller (USB Power Deliver specification ch. 2.4) it will have
+second device also for the other plug. Both plugs may have alternate modes as
+described in USB Type-C and USB Power Delivery specifications.
+
+What:		/sys/class/typec/<port>-cable/type
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows if the cable is active.
+		Valid values: active, passive
+
+What:		/sys/class/typec/<port>-cable/plug_type
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows type of the plug on the cable:
+		- type-a - Standard A
+		- type-b - Standard B
+		- type-c
+		- captive
+
+What:		/sys/class/typec/<port>-cable/identity/
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		This directory appears only if the port device driver is capable
+		of showing the result of Discover Identity USB power delivery
+		command. That will not always be possible even when USB power
+		delivery is supported. If the directory exists, it will have an
+		attribute for every VDO returned by Discover Identity command.
+
+What:		/sys/class/typec/<port>-cable/identity/id_header
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		ID Header VDO part of Discover Identity command result. The
+		value will show 0 until Discover Identity command result becomes
+		available. The value can be polled.
+
+What:		/sys/class/typec/<port>-cable/identity/cert_stat
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Cert Stat VDO part of Discover Identity command result. The
+		value will show 0 until Discover Identity command result becomes
+		available. The value can be polled.
+
+What:		/sys/class/typec/<port>-cable/identity/product
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Product VDO part of Discover Identity command result. The value
+		will show 0 until Discover Identity command result becomes
+		available. The value can be polled.
+
+
+Alternate Mode devices.
+
+The alternate modes will have Standard or Vendor ID (SVID) assigned by USB-IF.
+The ports, partners and cable plugs can have alternate modes. A supported SVID
+will consist of a set of modes. Every SVID a port/partner/plug supports will
+have a device created for it, and every supported mode for a supported SVID will
+have its own directory under that device. Below <dev> refers to the device for
+the alternate mode.
+
+What:		/sys/class/typec/<port|partner|cable>/<dev>/svid
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		The SVID (Standard or Vendor ID) assigned by USB-IF for this
+		alternate mode.
+
+What:		/sys/class/typec/<port|partner|cable>/<dev>/mode<index>/
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Every supported mode will have its own directory. The name of
+		a mode will be "mode<index>" (for example mode1), where <index>
+		is the actual index to the mode VDO returned by Discover Modes
+		USB power delivery command.
+
+What:		/sys/class/typec/<port|partner|cable>/<dev>/mode<index>/description
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows description of the mode. The description is optional for
+		the drivers, just like with the Billboard Devices.
+
+What:		/sys/class/typec/<port|partner|cable>/<dev>/mode<index>/vdo
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows the VDO in hexadecimal returned by Discover Modes command
+		for this mode.
+
+What:		/sys/class/typec/<port|partner|cable>/<dev>/mode<index>/active
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows if the mode is active or not. The attribute can be used
+		for entering/exiting the mode with partners and cable plugs, and
+		with the port alternate modes it can be used for disabling
+		support for specific alternate modes. Entering/exiting modes is
+		supported as synchronous operation so write(2) to the attribute
+		does not return until the enter/exit mode operation has
+		finished. The attribute is notified when the mode is
+		entered/exited so poll(2) on the attribute wakes up.
+		Entering/exiting a mode will also generate uevent KOBJ_CHANGE.
+
+		Valid values: yes, no
+
+What:		/sys/class/typec/<port>/<dev>/mode<index>/supported_roles
+Date:		April 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Space separated list of the supported roles.
+
+		This attribute is available for the devices describing the
+		alternate modes a port supports, and it will not be exposed with
+		the devices presenting the alternate modes the partners or cable
+		plugs support.
+
+		Valid values: source, sink

+ 9 - 0
Documentation/ABI/testing/sysfs-platform-chipidea-usb2

@@ -0,0 +1,9 @@
+What:		/sys/bus/platform/devices/ci_hdrc.0/role
+Date:		Mar 2017
+Contact:	Peter Chen <peter.chen@nxp.com>
+Description:
+		It returns string "gadget" or "host" when read it, it indicates
+		current controller role.
+
+		It will do role switch when write "gadget" or "host" to it.
+		Only controller at dual-role configuration supports writing.

+ 15 - 0
Documentation/ABI/testing/sysfs-platform-renesas_usb3

@@ -0,0 +1,15 @@
+What:		/sys/devices/platform/<renesas_usb3's name>/role
+Date:		March 2017
+KernelVersion:	4.13
+Contact:	Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
+Description:
+		This file can be read and write.
+		The file can show/change the drd mode of usb.
+
+		Write the following string to change the mode:
+		 "host" - switching mode from peripheral to host.
+		 "peripheral" - switching mode from host to peripheral.
+
+		Read the file, then it shows the following strings:
+		 "host" - The mode is host now.
+		 "peripheral" - The mode is peripheral now.

+ 80 - 13
Documentation/devicetree/bindings/phy/phy-mt65xx-usb.txt

@@ -6,12 +6,11 @@ This binding describes a usb3.0 phy for mt65xx platforms of Medaitek SoC.
 Required properties (controller (parent) node):
  - compatible	: should be one of
 		  "mediatek,mt2701-u3phy"
+		  "mediatek,mt2712-u3phy"
 		  "mediatek,mt8173-u3phy"
- - reg		: offset and length of register for phy, exclude port's
-		  register.
- - clocks	: a list of phandle + clock-specifier pairs, one for each
-		  entry in clock-names
- - clock-names	: must contain
+ - clocks	: (deprecated, use port's clocks instead) a list of phandle +
+		  clock-specifier pairs, one for each entry in clock-names
+ - clock-names	: (deprecated, use port's one instead) must contain
 		  "u3phya_ref": for reference clock of usb3.0 analog phy.
 
 Required nodes	: a sub-node is required for each port the controller
@@ -19,8 +18,19 @@ Required nodes	: a sub-node is required for each port the controller
 		  'reg' property is used inside these nodes to describe
 		  the controller's topology.
 
+Optional properties (controller (parent) node):
+ - reg		: offset and length of register shared by multiple ports,
+		  exclude port's private register. It is needed on mt2701
+		  and mt8173, but not on mt2712.
+
 Required properties (port (child) node):
 - reg		: address and length of the register set for the port.
+- clocks	: a list of phandle + clock-specifier pairs, one for each
+		  entry in clock-names
+- clock-names	: must contain
+		  "ref": 48M reference clock for HighSpeed analog phy; and 26M
+			reference clock for SuperSpeed analog phy, sometimes is
+			24M, 25M or 27M, depended on platform.
 - #phy-cells	: should be 1 (See second example)
 		  cell after port phandle is phy type from:
 			- PHY_TYPE_USB2
@@ -31,21 +41,31 @@ Example:
 u3phy: usb-phy@11290000 {
 	compatible = "mediatek,mt8173-u3phy";
 	reg = <0 0x11290000 0 0x800>;
-	clocks = <&apmixedsys CLK_APMIXED_REF2USB_TX>;
-	clock-names = "u3phya_ref";
 	#address-cells = <2>;
 	#size-cells = <2>;
 	ranges;
 	status = "okay";
 
-	phy_port0: port@11290800 {
-		reg = <0 0x11290800 0 0x800>;
+	u2port0: usb-phy@11290800 {
+		reg = <0 0x11290800 0 0x100>;
+		clocks = <&apmixedsys CLK_APMIXED_REF2USB_TX>;
+		clock-names = "ref";
 		#phy-cells = <1>;
 		status = "okay";
 	};
 
-	phy_port1: port@11291000 {
-		reg = <0 0x11291000 0 0x800>;
+	u3port0: usb-phy@11290900 {
+		reg = <0 0x11290800 0 0x700>;
+		clocks = <&clk26m>;
+		clock-names = "ref";
+		#phy-cells = <1>;
+		status = "okay";
+	};
+
+	u2port1: usb-phy@11291000 {
+		reg = <0 0x11291000 0 0x100>;
+		clocks = <&apmixedsys CLK_APMIXED_REF2USB_TX>;
+		clock-names = "ref";
 		#phy-cells = <1>;
 		status = "okay";
 	};
@@ -64,7 +84,54 @@ Example:
 
 usb30: usb@11270000 {
 	...
-	phys = <&phy_port0 PHY_TYPE_USB3>;
-	phy-names = "usb3-0";
+	phys = <&u2port0 PHY_TYPE_USB2>, <&u3port0 PHY_TYPE_USB3>;
+	phy-names = "usb2-0", "usb3-0";
 	...
 };
+
+
+Layout differences of banks between mt8173/mt2701 and mt2712
+-------------------------------------------------------------
+mt8173 and mt2701:
+port        offset    bank
+shared      0x0000    SPLLC
+            0x0100    FMREG
+u2 port0    0x0800    U2PHY_COM
+u3 port0    0x0900    U3PHYD
+            0x0a00    U3PHYD_BANK2
+            0x0b00    U3PHYA
+            0x0c00    U3PHYA_DA
+u2 port1    0x1000    U2PHY_COM
+u3 port1    0x1100    U3PHYD
+            0x1200    U3PHYD_BANK2
+            0x1300    U3PHYA
+            0x1400    U3PHYA_DA
+u2 port2    0x1800    U2PHY_COM
+            ...
+
+mt2712:
+port        offset    bank
+u2 port0    0x0000    MISC
+            0x0100    FMREG
+            0x0300    U2PHY_COM
+u3 port0    0x0700    SPLLC
+            0x0800    CHIP
+            0x0900    U3PHYD
+            0x0a00    U3PHYD_BANK2
+            0x0b00    U3PHYA
+            0x0c00    U3PHYA_DA
+u2 port1    0x1000    MISC
+            0x1100    FMREG
+            0x1300    U2PHY_COM
+u3 port1    0x1700    SPLLC
+            0x1800    CHIP
+            0x1900    U3PHYD
+            0x1a00    U3PHYD_BANK2
+            0x1b00    U3PHYA
+            0x1c00    U3PHYA_DA
+u2 port2    0x2000    MISC
+            ...
+
+    SPLLC shared by u3 ports and FMREG shared by u2 ports on
+mt8173/mt2701 are put back into each port; a new bank MISC for
+u2 ports and CHIP for u3 ports are added on mt2712.

+ 6 - 0
Documentation/devicetree/bindings/phy/phy-rockchip-inno-usb2.txt

@@ -2,6 +2,7 @@ ROCKCHIP USB2.0 PHY WITH INNO IP BLOCK
 
 Required properties (phy (parent) node):
  - compatible : should be one of the listed compatibles:
+	* "rockchip,rk3328-usb2phy"
 	* "rockchip,rk3366-usb2phy"
 	* "rockchip,rk3399-usb2phy"
  - reg : the address offset of grf for usb-phy configuration.
@@ -11,6 +12,11 @@ Required properties (phy (parent) node):
 Optional properties:
  - clocks : phandle + phy specifier pair, for the input clock of phy.
  - clock-names : input clock name of phy, must be "phyclk".
+ - assigned-clocks : phandle of usb 480m clock.
+ - assigned-clock-parents : parent of usb 480m clock, select between
+		 usb-phy output 480m and xin24m.
+		 Refer to clk/clock-bindings.txt for generic clock
+		 consumer properties.
 
 Required nodes : a sub-node is required for each port the phy provides.
 		 The sub-node name is used to identify host or otg port,

+ 106 - 0
Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt

@@ -0,0 +1,106 @@
+Qualcomm QMP PHY controller
+===========================
+
+QMP phy controller supports physical layer functionality for a number of
+controllers on Qualcomm chipsets, such as, PCIe, UFS, and USB.
+
+Required properties:
+ - compatible: compatible list, contains:
+	       "qcom,msm8996-qmp-pcie-phy" for 14nm PCIe phy on msm8996,
+	       "qcom,msm8996-qmp-usb3-phy" for 14nm USB3 phy on msm8996.
+
+ - reg: offset and length of register set for PHY's common serdes block.
+
+ - #clock-cells: must be 1
+    - Phy pll outputs a bunch of clocks for Tx, Rx and Pipe
+      interface (for pipe based PHYs). These clock are then gate-controlled
+      by gcc.
+ - #address-cells: must be 1
+ - #size-cells: must be 1
+ - ranges: must be present
+
+ - clocks: a list of phandles and clock-specifier pairs,
+	   one for each entry in clock-names.
+ - clock-names: "cfg_ahb" for phy config clock,
+		"aux" for phy aux clock,
+		"ref" for 19.2 MHz ref clk,
+		For "qcom,msm8996-qmp-pcie-phy" must contain:
+			"aux", "cfg_ahb", "ref".
+		For "qcom,msm8996-qmp-usb3-phy" must contain:
+			"aux", "cfg_ahb", "ref".
+
+ - resets: a list of phandles and reset controller specifier pairs,
+	   one for each entry in reset-names.
+ - reset-names: "phy" for reset of phy block,
+		"common" for phy common block reset,
+		"cfg" for phy's ahb cfg block reset (Optional).
+		For "qcom,msm8996-qmp-pcie-phy" must contain:
+		 "phy", "common", "cfg".
+		For "qcom,msm8996-qmp-usb3-phy" must contain
+		 "phy", "common".
+
+ - vdda-phy-supply: Phandle to a regulator supply to PHY core block.
+ - vdda-pll-supply: Phandle to 1.8V regulator supply to PHY refclk pll block.
+
+Optional properties:
+ - vddp-ref-clk-supply: Phandle to a regulator supply to any specific refclk
+			pll block.
+
+Required nodes:
+ - Each device node of QMP phy is required to have as many child nodes as
+   the number of lanes the PHY has.
+
+Required properties for child node:
+ - reg: list of offset and length pairs of register sets for PHY blocks -
+	tx, rx and pcs.
+
+ - #phy-cells: must be 0
+
+ - clocks: a list of phandles and clock-specifier pairs,
+	   one for each entry in clock-names.
+ - clock-names: Must contain following for pcie and usb qmp phys:
+		 "pipe<lane-number>" for pipe clock specific to each lane.
+
+ - resets: a list of phandles and reset controller specifier pairs,
+	   one for each entry in reset-names.
+ - reset-names: Must contain following for pcie qmp phys:
+		 "lane<lane-number>" for reset specific to each lane.
+
+Example:
+	phy@34000 {
+		compatible = "qcom,msm8996-qmp-pcie-phy";
+		reg = <0x34000 0x488>;
+		#clock-cells = <1>;
+		#address-cells = <1>;
+		#size-cells = <1>;
+		ranges;
+
+		clocks = <&gcc GCC_PCIE_PHY_AUX_CLK>,
+			<&gcc GCC_PCIE_PHY_CFG_AHB_CLK>,
+			<&gcc GCC_PCIE_CLKREF_CLK>;
+		clock-names = "aux", "cfg_ahb", "ref";
+
+		vdda-phy-supply = <&pm8994_l28>;
+		vdda-pll-supply = <&pm8994_l12>;
+
+		resets = <&gcc GCC_PCIE_PHY_BCR>,
+			<&gcc GCC_PCIE_PHY_COM_BCR>,
+			<&gcc GCC_PCIE_PHY_COM_NOCSR_BCR>;
+		reset-names = "phy", "common", "cfg";
+
+		pciephy_0: lane@35000 {
+			reg = <0x35000 0x130>,
+				<0x35200 0x200>,
+				<0x35400 0x1dc>;
+			#phy-cells = <0>;
+
+			clocks = <&gcc GCC_PCIE_0_PIPE_CLK>;
+			clock-names = "pipe0";
+			resets = <&gcc GCC_PCIE_0_PHY_BCR>;
+			reset-names = "lane0";
+		};
+
+		pciephy_1: lane@36000 {
+		...
+		...
+	};

+ 43 - 0
Documentation/devicetree/bindings/phy/qcom-qusb2-phy.txt

@@ -0,0 +1,43 @@
+Qualcomm QUSB2 phy controller
+=============================
+
+QUSB2 controller supports LS/FS/HS usb connectivity on Qualcomm chipsets.
+
+Required properties:
+ - compatible: compatible list, contains "qcom,msm8996-qusb2-phy".
+ - reg: offset and length of the PHY register set.
+ - #phy-cells: must be 0.
+
+ - clocks: a list of phandles and clock-specifier pairs,
+	   one for each entry in clock-names.
+ - clock-names: must be "cfg_ahb" for phy config clock,
+			"ref" for 19.2 MHz ref clk,
+			"iface" for phy interface clock (Optional).
+
+ - vdda-pll-supply: Phandle to 1.8V regulator supply to PHY refclk pll block.
+ - vdda-phy-dpdm-supply: Phandle to 3.1V regulator supply to Dp/Dm port signals.
+
+ - resets: Phandle to reset to phy block.
+
+Optional properties:
+ - nvmem-cells: Phandle to nvmem cell that contains 'HS Tx trim'
+		tuning parameter value for qusb2 phy.
+
+ - qcom,tcsr-syscon: Phandle to TCSR syscon register region.
+
+Example:
+	hsusb_phy: phy@7411000 {
+		compatible = "qcom,msm8996-qusb2-phy";
+		reg = <0x7411000 0x180>;
+		#phy-cells = <0>;
+
+		clocks = <&gcc GCC_USB_PHY_CFG_AHB2PHY_CLK>,
+			<&gcc GCC_RX1_USB2_CLKREF_CLK>,
+		clock-names = "cfg_ahb", "ref";
+
+		vdda-pll-supply = <&pm8994_l12>;
+		vdda-phy-dpdm-supply = <&pm8994_l24>;
+
+		resets = <&gcc GCC_QUSB2PHY_PRIM_BCR>;
+		nvmem-cells = <&qusb2p_hstx_trim>;
+        };

+ 1 - 0
Documentation/devicetree/bindings/phy/rockchip-usb-phy.txt

@@ -30,6 +30,7 @@ Optional Properties:
 - reset-names: Only allow the following entries:
  - phy-reset
 - resets: Must contain an entry for each entry in reset-names.
+- vbus-supply: power-supply phandle for vbus power source
 
 Example:
 

+ 1 - 0
Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt

@@ -15,6 +15,7 @@ Required properties:
 - reg : a list of offset + length pairs
 - reg-names :
   * "phy_ctrl"
+  * "pmu0" for H3, V3s and A64
   * "pmu1"
   * "pmu2" for sun4i, sun6i or sun7i
 - #phy-cells : from the generic phy bindings, must be 1

+ 4 - 0
Documentation/devicetree/bindings/soc/rockchip/grf.txt

@@ -8,6 +8,8 @@ From RK3368 SoCs, the GRF is divided into two sections,
 - SGRF, used for general secure system,
 - PMUGRF, used for always on system
 
+On RK3328 SoCs, the GRF adds a section for USB2PHYGRF,
+
 Required Properties:
 
 - compatible: GRF should be one of the following:
@@ -23,6 +25,8 @@ Required Properties:
    - "rockchip,rk3399-pmugrf", "syscon": for rk3399
 - compatible: SGRF should be one of the following
    - "rockchip,rk3288-sgrf", "syscon": for rk3288
+- compatible: USB2PHYGRF should be one of the followings
+   - "rockchip,rk3328-usb2phy-grf", "syscon": for rk3328
 - reg: physical base address of the controller and length of memory mapped
   region.
 

+ 3 - 1
Documentation/devicetree/bindings/usb/ehci-orion.txt

@@ -1,7 +1,9 @@
 * EHCI controller, Orion Marvell variants
 
 Required properties:
-- compatible: must be "marvell,orion-ehci"
+- compatible: must be one of the following
+	"marvell,orion-ehci"
+	"marvell,armada-3700-ehci"
 - reg: physical base address of the controller and length of memory mapped
   region.
 - interrupts: The EHCI interrupt

+ 1 - 0
Documentation/devicetree/bindings/usb/generic.txt

@@ -22,6 +22,7 @@ Optional properties:
 			property is used if any real OTG features(HNP/SRP/ADP)
 			is enabled, if ADP is required, otg-rev should be
 			0x0200 or above.
+ - companion: phandle of a companion
  - hnp-disable: tells OTG controllers we want to disable OTG HNP, normally HNP
 			is the basic function of real OTG except you want it
 			to be a srp-capable only B device.

+ 2 - 2
Documentation/media/v4l-drivers/philips.rst

@@ -145,8 +145,8 @@ dev_hint
 
    A camera is specified by its type (the number from the camera model,
    like PCA645, PCVC750VC, etc) and optionally the serial number (visible
-   in /proc/bus/usb/devices). A hint consists of a string with the following
-   format::
+   in /sys/kernel/debug/usb/devices). A hint consists of a string with the
+   following format::
 
       [type[.serialnumber]:]node
 

+ 184 - 0
Documentation/usb/typec.rst

@@ -0,0 +1,184 @@
+
+USB Type-C connector class
+==========================
+
+Introduction
+------------
+
+The typec class is meant for describing the USB Type-C ports in a system to the
+user space in unified fashion. The class is designed to provide nothing else
+except the user space interface implementation in hope that it can be utilized
+on as many platforms as possible.
+
+The platforms are expected to register every USB Type-C port they have with the
+class. In a normal case the registration will be done by a USB Type-C or PD PHY
+driver, but it may be a driver for firmware interface such as UCSI, driver for
+USB PD controller or even driver for Thunderbolt3 controller. This document
+considers the component registering the USB Type-C ports with the class as "port
+driver".
+
+On top of showing the capabilities, the class also offer user space control over
+the roles and alternate modes of ports, partners and cable plugs when the port
+driver is capable of supporting those features.
+
+The class provides an API for the port drivers described in this document. The
+attributes are described in Documentation/ABI/testing/sysfs-class-typec.
+
+User space interface
+--------------------
+Every port will be presented as its own device under /sys/class/typec/. The
+first port will be named "port0", the second "port1" and so on.
+
+When connected, the partner will be presented also as its own device under
+/sys/class/typec/. The parent of the partner device will always be the port it
+is attached to. The partner attached to port "port0" will be named
+"port0-partner". Full path to the device would be
+/sys/class/typec/port0/port0-partner/.
+
+The cable and the two plugs on it may also be optionally presented as their own
+devices under /sys/class/typec/. The cable attached to the port "port0" port
+will be named port0-cable and the plug on the SOP Prime end (see USB Power
+Delivery Specification ch. 2.4) will be named "port0-plug0" and on the SOP
+Double Prime end "port0-plug1". The parent of a cable will always be the port,
+and the parent of the cable plugs will always be the cable.
+
+If the port, partner or cable plug supports Alternate Modes, every supported
+Alternate Mode SVID will have their own device describing them. Note that the
+Alternate Mode devices will not be attached to the typec class. The parent of an
+alternate mode will be the device that supports it, so for example an alternate
+mode of port0-partner will be presented under /sys/class/typec/port0-partner/.
+Every mode that is supported will have its own group under the Alternate Mode
+device named "mode<index>", for example /sys/class/typec/port0/<alternate
+mode>/mode1/. The requests for entering/exiting a mode can be done with "active"
+attribute file in that group.
+
+Driver API
+----------
+
+Registering the ports
+~~~~~~~~~~~~~~~~~~~~~
+
+The port drivers will describe every Type-C port they control with struct
+typec_capability data structure, and register them with the following API:
+
+.. kernel-doc:: drivers/usb/typec/typec.c
+   :functions: typec_register_port typec_unregister_port
+
+When registering the ports, the prefer_role member in struct typec_capability
+deserves special notice. If the port that is being registered does not have
+initial role preference, which means the port does not execute Try.SNK or
+Try.SRC by default, the member must have value TYPEC_NO_PREFERRED_ROLE.
+Otherwise if the port executes Try.SNK by default, the member must have value
+TYPEC_DEVICE, and with Try.SRC the value must be TYPEC_HOST.
+
+Registering Partners
+~~~~~~~~~~~~~~~~~~~~
+
+After successful connection of a partner, the port driver needs to register the
+partner with the class. Details about the partner need to be described in struct
+typec_partner_desc. The class copies the details of the partner during
+registration. The class offers the following API for registering/unregistering
+partners.
+
+.. kernel-doc:: drivers/usb/typec/typec.c
+   :functions: typec_register_partner typec_unregister_partner
+
+The class will provide a handle to struct typec_partner if the registration was
+successful, or NULL.
+
+If the partner is USB Power Delivery capable, and the port driver is able to
+show the result of Discover Identity command, the partner descriptor structure
+should include handle to struct usb_pd_identity instance. The class will then
+create a sysfs directory for the identity under the partner device. The result
+of Discover Identity command can then be reported with the following API:
+
+.. kernel-doc:: drivers/usb/typec/typec.c
+   :functions: typec_partner_set_identity
+
+Registering Cables
+~~~~~~~~~~~~~~~~~~
+
+After successful connection of a cable that supports USB Power Delivery
+Structured VDM "Discover Identity", the port driver needs to register the cable
+and one or two plugs, depending if there is CC Double Prime controller present
+in the cable or not. So a cable capable of SOP Prime communication, but not SOP
+Double Prime communication, should only have one plug registered. For more
+information about SOP communication, please read chapter about it from the
+latest USB Power Delivery specification.
+
+The plugs are represented as their own devices. The cable is registered first,
+followed by registration of the cable plugs. The cable will be the parent device
+for the plugs. Details about the cable need to be described in struct
+typec_cable_desc and about a plug in struct typec_plug_desc. The class copies
+the details during registration. The class offers the following API for
+registering/unregistering cables and their plugs:
+
+.. kernel-doc:: drivers/usb/typec/typec.c
+   :functions: typec_register_cable typec_unregister_cable typec_register_plug
+   typec_unregister_plug
+
+The class will provide a handle to struct typec_cable and struct typec_plug if
+the registration is successful, or NULL if it isn't.
+
+If the cable is USB Power Delivery capable, and the port driver is able to show
+the result of Discover Identity command, the cable descriptor structure should
+include handle to struct usb_pd_identity instance. The class will then create a
+sysfs directory for the identity under the cable device. The result of Discover
+Identity command can then be reported with the following API:
+
+.. kernel-doc:: drivers/usb/typec/typec.c
+   :functions: typec_cable_set_identity
+
+Notifications
+~~~~~~~~~~~~~
+
+When the partner has executed a role change, or when the default roles change
+during connection of a partner or cable, the port driver must use the following
+APIs to report it to the class:
+
+.. kernel-doc:: drivers/usb/typec/typec.c
+   :functions: typec_set_data_role typec_set_pwr_role typec_set_vconn_role
+   typec_set_pwr_opmode
+
+Alternate Modes
+~~~~~~~~~~~~~~~
+
+USB Type-C ports, partners and cable plugs may support Alternate Modes. Each
+Alternate Mode will have identifier called SVID, which is either a Standard ID
+given by USB-IF or vendor ID, and each supported SVID can have 1 - 6 modes. The
+class provides struct typec_mode_desc for describing individual mode of a SVID,
+and struct typec_altmode_desc which is a container for all the supported modes.
+
+Ports that support Alternate Modes need to register each SVID they support with
+the following API:
+
+.. kernel-doc:: drivers/usb/typec/typec.c
+   :functions: typec_port_register_altmode
+
+If a partner or cable plug provides a list of SVIDs as response to USB Power
+Delivery Structured VDM Discover SVIDs message, each SVID needs to be
+registered.
+
+API for the partners:
+
+.. kernel-doc:: drivers/usb/typec/typec.c
+   :functions: typec_partner_register_altmode
+
+API for the Cable Plugs:
+
+.. kernel-doc:: drivers/usb/typec/typec.c
+   :functions: typec_plug_register_altmode
+
+So ports, partners and cable plugs will register the alternate modes with their
+own functions, but the registration will always return a handle to struct
+typec_altmode on success, or NULL. The unregistration will happen with the same
+function:
+
+.. kernel-doc:: drivers/usb/typec/typec.c
+   :functions: typec_unregister_altmode
+
+If a partner or cable plug enters or exits a mode, the port driver needs to
+notify the class with the following API:
+
+.. kernel-doc:: drivers/usb/typec/typec.c
+   :functions: typec_altmode_update_active

+ 9 - 0
MAINTAINERS

@@ -13248,6 +13248,15 @@ F:	drivers/usb/
 F:	include/linux/usb.h
 F:	include/linux/usb/
 
+USB TYPEC SUBSYSTEM
+M:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+L:	linux-usb@vger.kernel.org
+S:	Maintained
+F:	Documentation/ABI/testing/sysfs-class-typec
+F:	Documentation/usb/typec.rst
+F:	drivers/usb/typec/
+F:	include/linux/usb/typec.h
+
 USB UHCI DRIVER
 M:	Alan Stern <stern@rowland.harvard.edu>
 L:	linux-usb@vger.kernel.org

+ 6 - 0
arch/arm64/boot/dts/marvell/armada-3720-db.dts

@@ -116,6 +116,12 @@
 	status = "okay";
 };
 
+/* CON27 */
+&usb2 {
+	status = "okay";
+};
+
+
 &mdio {
 	status = "okay";
 	phy0: ethernet-phy@0 {

+ 7 - 0
arch/arm64/boot/dts/marvell/armada-37xx.dtsi

@@ -200,6 +200,13 @@
 				status = "disabled";
 			};
 
+			usb2: usb@5e000 {
+				compatible = "marvell,armada-3700-ehci";
+				reg = <0x5e000 0x2000>;
+				interrupts = <GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH>;
+				status = "disabled";
+			};
+
 			xor@60900 {
 				compatible = "marvell,armada-3700-xor";
 				reg = <0x60900 0x100

+ 1 - 0
drivers/Makefile

@@ -104,6 +104,7 @@ obj-$(CONFIG_USB_PHY)		+= usb/
 obj-$(CONFIG_USB)		+= usb/
 obj-$(CONFIG_PCI)		+= usb/
 obj-$(CONFIG_USB_GADGET)	+= usb/
+obj-$(CONFIG_OF)		+= usb/
 obj-$(CONFIG_SERIO)		+= input/serio/
 obj-$(CONFIG_GAMEPORT)		+= input/gameport/
 obj-$(CONFIG_INPUT)		+= input/

+ 19 - 0
drivers/phy/Kconfig

@@ -439,6 +439,25 @@ config PHY_STIH407_USB
 	  Enable this support to enable the picoPHY device used by USB2
 	  and USB3 controllers on STMicroelectronics STiH407 SoC families.
 
+config PHY_QCOM_QMP
+	tristate "Qualcomm QMP PHY Driver"
+	depends on OF && COMMON_CLK && (ARCH_QCOM || COMPILE_TEST)
+	select GENERIC_PHY
+	help
+	  Enable this to support the QMP PHY transceiver that is used
+	  with controllers such as PCIe, UFS, and USB on Qualcomm chips.
+
+config PHY_QCOM_QUSB2
+	tristate "Qualcomm QUSB2 PHY Driver"
+	depends on OF && (ARCH_QCOM || COMPILE_TEST)
+	depends on NVMEM || !NVMEM
+	select GENERIC_PHY
+	help
+	  Enable this to support the HighSpeed QUSB2 PHY transceiver for USB
+	  controllers on Qualcomm chips. This driver supports the high-speed
+	  PHY which is usually paired with either the ChipIdea or Synopsys DWC3
+	  USB IPs on MSM SOCs.
+
 config PHY_QCOM_UFS
 	tristate "Qualcomm UFS PHY driver"
 	depends on OF && ARCH_QCOM

+ 2 - 0
drivers/phy/Makefile

@@ -50,6 +50,8 @@ obj-$(CONFIG_PHY_ST_SPEAR1310_MIPHY)	+= phy-spear1310-miphy.o
 obj-$(CONFIG_PHY_ST_SPEAR1340_MIPHY)	+= phy-spear1340-miphy.o
 obj-$(CONFIG_PHY_XGENE)			+= phy-xgene.o
 obj-$(CONFIG_PHY_STIH407_USB)		+= phy-stih407-usb.o
+obj-$(CONFIG_PHY_QCOM_QMP)		+= phy-qcom-qmp.o
+obj-$(CONFIG_PHY_QCOM_QUSB2)		+= phy-qcom-qusb2.o
 obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs.o
 obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs-qmp-20nm.o
 obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs-qmp-14nm.o

+ 49 - 20
drivers/phy/phy-bcm-ns-usb3.c

@@ -2,6 +2,7 @@
  * Broadcom Northstar USB 3.0 PHY Driver
  *
  * Copyright (C) 2016 Rafał Miłecki <rafal@milecki.pl>
+ * Copyright (C) 2016 Broadcom
  *
  * All magic values used for initialization (and related comments) were obtained
  * from Broadcom's SDK:
@@ -23,6 +24,23 @@
 
 #define BCM_NS_USB3_MII_MNG_TIMEOUT_US	1000	/* usecs */
 
+#define BCM_NS_USB3_PHY_BASE_ADDR_REG	0x1f
+#define BCM_NS_USB3_PHY_PLL30_BLOCK	0x8000
+#define BCM_NS_USB3_PHY_TX_PMD_BLOCK	0x8040
+#define BCM_NS_USB3_PHY_PIPE_BLOCK	0x8060
+
+/* Registers of PLL30 block */
+#define BCM_NS_USB3_PLL_CONTROL		0x01
+#define BCM_NS_USB3_PLLA_CONTROL0	0x0a
+#define BCM_NS_USB3_PLLA_CONTROL1	0x0b
+
+/* Registers of TX PMD block */
+#define BCM_NS_USB3_TX_PMD_CONTROL1	0x01
+
+/* Registers of PIPE block */
+#define BCM_NS_USB3_LFPS_CMP		0x02
+#define BCM_NS_USB3_LFPS_DEGLITCH	0x03
+
 enum bcm_ns_family {
 	BCM_NS_UNKNOWN,
 	BCM_NS_AX,
@@ -76,8 +94,10 @@ static inline int bcm_ns_usb3_mii_mng_wait_idle(struct bcm_ns_usb3 *usb3)
 				    usecs_to_jiffies(BCM_NS_USB3_MII_MNG_TIMEOUT_US));
 }
 
-static int bcm_ns_usb3_mii_mng_write32(struct bcm_ns_usb3 *usb3, u32 value)
+static int bcm_ns_usb3_mdio_phy_write(struct bcm_ns_usb3 *usb3, u16 reg,
+				      u16 value)
 {
+	u32 tmp = 0;
 	int err;
 
 	err = bcm_ns_usb3_mii_mng_wait_idle(usb3);
@@ -86,7 +106,11 @@ static int bcm_ns_usb3_mii_mng_write32(struct bcm_ns_usb3 *usb3, u32 value)
 		return err;
 	}
 
-	writel(value, usb3->ccb_mii + BCMA_CCB_MII_MNG_CMD_DATA);
+	/* TODO: Use a proper MDIO bus layer */
+	tmp |= 0x58020000; /* Magic value for MDIO PHY write */
+	tmp |= reg << 18;
+	tmp |= value;
+	writel(tmp, usb3->ccb_mii + BCMA_CCB_MII_MNG_CMD_DATA);
 
 	return 0;
 }
@@ -102,21 +126,22 @@ static int bcm_ns_usb3_phy_init_ns_bx(struct bcm_ns_usb3 *usb3)
 	udelay(2);
 
 	/* USB3 PLL Block */
-	err = bcm_ns_usb3_mii_mng_write32(usb3, 0x587e8000);
+	err = bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG,
+					 BCM_NS_USB3_PHY_PLL30_BLOCK);
 	if (err < 0)
 		return err;
 
 	/* Assert Ana_Pllseq start */
-	bcm_ns_usb3_mii_mng_write32(usb3, 0x58061000);
+	bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLL_CONTROL, 0x1000);
 
 	/* Assert CML Divider ratio to 26 */
-	bcm_ns_usb3_mii_mng_write32(usb3, 0x582a6400);
+	bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL0, 0x6400);
 
 	/* Asserting PLL Reset */
-	bcm_ns_usb3_mii_mng_write32(usb3, 0x582ec000);
+	bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL1, 0xc000);
 
 	/* Deaaserting PLL Reset */
-	bcm_ns_usb3_mii_mng_write32(usb3, 0x582e8000);
+	bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL1, 0x8000);
 
 	/* Waiting MII Mgt interface idle */
 	bcm_ns_usb3_mii_mng_wait_idle(usb3);
@@ -125,22 +150,24 @@ static int bcm_ns_usb3_phy_init_ns_bx(struct bcm_ns_usb3 *usb3)
 	writel(0, usb3->dmp + BCMA_RESET_CTL);
 
 	/* PLL frequency monitor enable */
-	bcm_ns_usb3_mii_mng_write32(usb3, 0x58069000);
+	bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLL_CONTROL, 0x9000);
 
 	/* PIPE Block */
-	bcm_ns_usb3_mii_mng_write32(usb3, 0x587e8060);
+	bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG,
+				   BCM_NS_USB3_PHY_PIPE_BLOCK);
 
 	/* CMPMAX & CMPMINTH setting */
-	bcm_ns_usb3_mii_mng_write32(usb3, 0x580af30d);
+	bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_LFPS_CMP, 0xf30d);
 
 	/* DEGLITCH MIN & MAX setting */
-	bcm_ns_usb3_mii_mng_write32(usb3, 0x580e6302);
+	bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_LFPS_DEGLITCH, 0x6302);
 
 	/* TXPMD block */
-	bcm_ns_usb3_mii_mng_write32(usb3, 0x587e8040);
+	bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG,
+				   BCM_NS_USB3_PHY_TX_PMD_BLOCK);
 
 	/* Enabling SSC */
-	bcm_ns_usb3_mii_mng_write32(usb3, 0x58061003);
+	bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_TX_PMD_CONTROL1, 0x1003);
 
 	/* Waiting MII Mgt interface idle */
 	bcm_ns_usb3_mii_mng_wait_idle(usb3);
@@ -159,22 +186,24 @@ static int bcm_ns_usb3_phy_init_ns_ax(struct bcm_ns_usb3 *usb3)
 	udelay(2);
 
 	/* PLL30 block */
-	err = bcm_ns_usb3_mii_mng_write32(usb3, 0x587e8000);
+	err = bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG,
+					 BCM_NS_USB3_PHY_PLL30_BLOCK);
 	if (err < 0)
 		return err;
 
-	bcm_ns_usb3_mii_mng_write32(usb3, 0x582a6400);
+	bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL0, 0x6400);
 
-	bcm_ns_usb3_mii_mng_write32(usb3, 0x587e80e0);
+	bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, 0x80e0);
 
-	bcm_ns_usb3_mii_mng_write32(usb3, 0x580a009c);
+	bcm_ns_usb3_mdio_phy_write(usb3, 0x02, 0x009c);
 
 	/* Enable SSC */
-	bcm_ns_usb3_mii_mng_write32(usb3, 0x587e8040);
+	bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG,
+				   BCM_NS_USB3_PHY_TX_PMD_BLOCK);
 
-	bcm_ns_usb3_mii_mng_write32(usb3, 0x580a21d3);
+	bcm_ns_usb3_mdio_phy_write(usb3, 0x02, 0x21d3);
 
-	bcm_ns_usb3_mii_mng_write32(usb3, 0x58061003);
+	bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_TX_PMD_CONTROL1, 0x1003);
 
 	/* Waiting MII Mgt interface idle */
 	bcm_ns_usb3_mii_mng_wait_idle(usb3);

+ 3 - 3
drivers/phy/phy-exynos-dp-video.c

@@ -14,12 +14,12 @@
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/mfd/syscon.h>
-#include <linux/mfd/syscon/exynos5-pmu.h>
 #include <linux/of.h>
 #include <linux/of_address.h>
 #include <linux/phy/phy.h>
 #include <linux/platform_device.h>
 #include <linux/regmap.h>
+#include <linux/soc/samsung/exynos-regs-pmu.h>
 
 struct exynos_dp_video_phy_drvdata {
 	u32 phy_ctrl_offset;
@@ -36,7 +36,7 @@ static int exynos_dp_video_phy_power_on(struct phy *phy)
 
 	/* Disable power isolation on DP-PHY */
 	return regmap_update_bits(state->regs, state->drvdata->phy_ctrl_offset,
-				  EXYNOS5_PHY_ENABLE, EXYNOS5_PHY_ENABLE);
+				  EXYNOS4_PHY_ENABLE, EXYNOS4_PHY_ENABLE);
 }
 
 static int exynos_dp_video_phy_power_off(struct phy *phy)
@@ -45,7 +45,7 @@ static int exynos_dp_video_phy_power_off(struct phy *phy)
 
 	/* Enable power isolation on DP-PHY */
 	return regmap_update_bits(state->regs, state->drvdata->phy_ctrl_offset,
-				  EXYNOS5_PHY_ENABLE, 0);
+				  EXYNOS4_PHY_ENABLE, 0);
 }
 
 static const struct phy_ops exynos_dp_video_phy_ops = {

+ 35 - 36
drivers/phy/phy-exynos-mipi-video.c

@@ -12,8 +12,6 @@
 #include <linux/err.h>
 #include <linux/io.h>
 #include <linux/kernel.h>
-#include <linux/mfd/syscon/exynos4-pmu.h>
-#include <linux/mfd/syscon/exynos5-pmu.h>
 #include <linux/module.h>
 #include <linux/of.h>
 #include <linux/of_address.h>
@@ -21,6 +19,7 @@
 #include <linux/phy/phy.h>
 #include <linux/regmap.h>
 #include <linux/spinlock.h>
+#include <linux/soc/samsung/exynos-regs-pmu.h>
 #include <linux/mfd/syscon.h>
 
 enum exynos_mipi_phy_id {
@@ -64,7 +63,7 @@ static const struct mipi_phy_device_desc s5pv210_mipi_phy = {
 		{
 			/* EXYNOS_MIPI_PHY_ID_CSIS0 */
 			.coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0,
-			.enable_val = EXYNOS4_MIPI_PHY_ENABLE,
+			.enable_val = EXYNOS4_PHY_ENABLE,
 			.enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0),
 			.enable_map = EXYNOS_MIPI_REGMAP_PMU,
 			.resetn_val = EXYNOS4_MIPI_PHY_SRESETN,
@@ -73,7 +72,7 @@ static const struct mipi_phy_device_desc s5pv210_mipi_phy = {
 		}, {
 			/* EXYNOS_MIPI_PHY_ID_DSIM0 */
 			.coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0,
-			.enable_val = EXYNOS4_MIPI_PHY_ENABLE,
+			.enable_val = EXYNOS4_PHY_ENABLE,
 			.enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0),
 			.enable_map = EXYNOS_MIPI_REGMAP_PMU,
 			.resetn_val = EXYNOS4_MIPI_PHY_MRESETN,
@@ -82,7 +81,7 @@ static const struct mipi_phy_device_desc s5pv210_mipi_phy = {
 		}, {
 			/* EXYNOS_MIPI_PHY_ID_CSIS1 */
 			.coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM1,
-			.enable_val = EXYNOS4_MIPI_PHY_ENABLE,
+			.enable_val = EXYNOS4_PHY_ENABLE,
 			.enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1),
 			.enable_map = EXYNOS_MIPI_REGMAP_PMU,
 			.resetn_val = EXYNOS4_MIPI_PHY_SRESETN,
@@ -91,7 +90,7 @@ static const struct mipi_phy_device_desc s5pv210_mipi_phy = {
 		}, {
 			/* EXYNOS_MIPI_PHY_ID_DSIM1 */
 			.coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS1,
-			.enable_val = EXYNOS4_MIPI_PHY_ENABLE,
+			.enable_val = EXYNOS4_PHY_ENABLE,
 			.enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1),
 			.enable_map = EXYNOS_MIPI_REGMAP_PMU,
 			.resetn_val = EXYNOS4_MIPI_PHY_MRESETN,
@@ -109,47 +108,47 @@ static const struct mipi_phy_device_desc exynos5420_mipi_phy = {
 		{
 			/* EXYNOS_MIPI_PHY_ID_CSIS0 */
 			.coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0,
-			.enable_val = EXYNOS5_PHY_ENABLE,
-			.enable_reg = EXYNOS5420_MIPI_PHY0_CONTROL,
+			.enable_val = EXYNOS4_PHY_ENABLE,
+			.enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(0),
 			.enable_map = EXYNOS_MIPI_REGMAP_PMU,
-			.resetn_val = EXYNOS5_MIPI_PHY_S_RESETN,
-			.resetn_reg = EXYNOS5420_MIPI_PHY0_CONTROL,
+			.resetn_val = EXYNOS4_MIPI_PHY_SRESETN,
+			.resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(0),
 			.resetn_map = EXYNOS_MIPI_REGMAP_PMU,
 		}, {
 			/* EXYNOS_MIPI_PHY_ID_DSIM0 */
 			.coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0,
-			.enable_val = EXYNOS5_PHY_ENABLE,
-			.enable_reg = EXYNOS5420_MIPI_PHY0_CONTROL,
+			.enable_val = EXYNOS4_PHY_ENABLE,
+			.enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(0),
 			.enable_map = EXYNOS_MIPI_REGMAP_PMU,
-			.resetn_val = EXYNOS5_MIPI_PHY_M_RESETN,
-			.resetn_reg = EXYNOS5420_MIPI_PHY0_CONTROL,
+			.resetn_val = EXYNOS4_MIPI_PHY_MRESETN,
+			.resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(0),
 			.resetn_map = EXYNOS_MIPI_REGMAP_PMU,
 		}, {
 			/* EXYNOS_MIPI_PHY_ID_CSIS1 */
 			.coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM1,
-			.enable_val = EXYNOS5_PHY_ENABLE,
-			.enable_reg = EXYNOS5420_MIPI_PHY1_CONTROL,
+			.enable_val = EXYNOS4_PHY_ENABLE,
+			.enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(1),
 			.enable_map = EXYNOS_MIPI_REGMAP_PMU,
-			.resetn_val = EXYNOS5_MIPI_PHY_S_RESETN,
-			.resetn_reg = EXYNOS5420_MIPI_PHY1_CONTROL,
+			.resetn_val = EXYNOS4_MIPI_PHY_SRESETN,
+			.resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(1),
 			.resetn_map = EXYNOS_MIPI_REGMAP_PMU,
 		}, {
 			/* EXYNOS_MIPI_PHY_ID_DSIM1 */
 			.coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS1,
-			.enable_val = EXYNOS5_PHY_ENABLE,
-			.enable_reg = EXYNOS5420_MIPI_PHY1_CONTROL,
+			.enable_val = EXYNOS4_PHY_ENABLE,
+			.enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(1),
 			.enable_map = EXYNOS_MIPI_REGMAP_PMU,
-			.resetn_val = EXYNOS5_MIPI_PHY_M_RESETN,
-			.resetn_reg = EXYNOS5420_MIPI_PHY1_CONTROL,
+			.resetn_val = EXYNOS4_MIPI_PHY_MRESETN,
+			.resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(1),
 			.resetn_map = EXYNOS_MIPI_REGMAP_PMU,
 		}, {
 			/* EXYNOS_MIPI_PHY_ID_CSIS2 */
 			.coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE,
-			.enable_val = EXYNOS5_PHY_ENABLE,
-			.enable_reg = EXYNOS5420_MIPI_PHY2_CONTROL,
+			.enable_val = EXYNOS4_PHY_ENABLE,
+			.enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(2),
 			.enable_map = EXYNOS_MIPI_REGMAP_PMU,
-			.resetn_val = EXYNOS5_MIPI_PHY_S_RESETN,
-			.resetn_reg = EXYNOS5420_MIPI_PHY2_CONTROL,
+			.resetn_val = EXYNOS4_MIPI_PHY_SRESETN,
+			.resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(2),
 			.resetn_map = EXYNOS_MIPI_REGMAP_PMU,
 		},
 	},
@@ -172,8 +171,8 @@ static const struct mipi_phy_device_desc exynos5433_mipi_phy = {
 		{
 			/* EXYNOS_MIPI_PHY_ID_CSIS0 */
 			.coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0,
-			.enable_val = EXYNOS5_PHY_ENABLE,
-			.enable_reg = EXYNOS5433_MIPI_PHY0_CONTROL,
+			.enable_val = EXYNOS4_PHY_ENABLE,
+			.enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0),
 			.enable_map = EXYNOS_MIPI_REGMAP_PMU,
 			.resetn_val = BIT(0),
 			.resetn_reg = EXYNOS5433_SYSREG_CAM0_MIPI_DPHY_CON,
@@ -181,8 +180,8 @@ static const struct mipi_phy_device_desc exynos5433_mipi_phy = {
 		}, {
 			/* EXYNOS_MIPI_PHY_ID_DSIM0 */
 			.coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0,
-			.enable_val = EXYNOS5_PHY_ENABLE,
-			.enable_reg = EXYNOS5433_MIPI_PHY0_CONTROL,
+			.enable_val = EXYNOS4_PHY_ENABLE,
+			.enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0),
 			.enable_map = EXYNOS_MIPI_REGMAP_PMU,
 			.resetn_val = BIT(0),
 			.resetn_reg = EXYNOS5433_SYSREG_DISP_MIPI_PHY,
@@ -190,8 +189,8 @@ static const struct mipi_phy_device_desc exynos5433_mipi_phy = {
 		}, {
 			/* EXYNOS_MIPI_PHY_ID_CSIS1 */
 			.coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE,
-			.enable_val = EXYNOS5_PHY_ENABLE,
-			.enable_reg = EXYNOS5433_MIPI_PHY1_CONTROL,
+			.enable_val = EXYNOS4_PHY_ENABLE,
+			.enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1),
 			.enable_map = EXYNOS_MIPI_REGMAP_PMU,
 			.resetn_val = BIT(1),
 			.resetn_reg = EXYNOS5433_SYSREG_CAM0_MIPI_DPHY_CON,
@@ -199,8 +198,8 @@ static const struct mipi_phy_device_desc exynos5433_mipi_phy = {
 		}, {
 			/* EXYNOS_MIPI_PHY_ID_DSIM1 */
 			.coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE,
-			.enable_val = EXYNOS5_PHY_ENABLE,
-			.enable_reg = EXYNOS5433_MIPI_PHY1_CONTROL,
+			.enable_val = EXYNOS4_PHY_ENABLE,
+			.enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1),
 			.enable_map = EXYNOS_MIPI_REGMAP_PMU,
 			.resetn_val = BIT(1),
 			.resetn_reg = EXYNOS5433_SYSREG_DISP_MIPI_PHY,
@@ -208,8 +207,8 @@ static const struct mipi_phy_device_desc exynos5433_mipi_phy = {
 		}, {
 			/* EXYNOS_MIPI_PHY_ID_CSIS2 */
 			.coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE,
-			.enable_val = EXYNOS5_PHY_ENABLE,
-			.enable_reg = EXYNOS5433_MIPI_PHY2_CONTROL,
+			.enable_val = EXYNOS4_PHY_ENABLE,
+			.enable_reg = EXYNOS4_MIPI_PHY_CONTROL(2),
 			.enable_map = EXYNOS_MIPI_REGMAP_PMU,
 			.resetn_val = BIT(0),
 			.resetn_reg = EXYNOS5433_SYSREG_CAM1_MIPI_DPHY_CON,

+ 2 - 6
drivers/phy/phy-exynos-pcie.c

@@ -14,8 +14,8 @@
 #include <linux/delay.h>
 #include <linux/io.h>
 #include <linux/iopoll.h>
+#include <linux/init.h>
 #include <linux/mfd/syscon.h>
-#include <linux/module.h>
 #include <linux/of.h>
 #include <linux/of_address.h>
 #include <linux/of_platform.h>
@@ -228,7 +228,6 @@ static const struct of_device_id exynos_pcie_phy_match[] = {
 	},
 	{},
 };
-MODULE_DEVICE_TABLE(of, exynos_pcie_phy_match);
 
 static int exynos_pcie_phy_probe(struct platform_device *pdev)
 {
@@ -278,8 +277,5 @@ static struct platform_driver exynos_pcie_phy_driver = {
 		.name		= "exynos_pcie_phy",
 	}
 };
-module_platform_driver(exynos_pcie_phy_driver);
 
-MODULE_DESCRIPTION("Samsung S5P/EXYNOS SoC PCIe PHY driver");
-MODULE_AUTHOR("Jaehoon Chung <jh80.chung@samsung.com>");
-MODULE_LICENSE("GPL v2");
+builtin_platform_driver(exynos_pcie_phy_driver);

+ 3 - 3
drivers/phy/phy-exynos5-usbdrd.c

@@ -22,9 +22,9 @@
 #include <linux/platform_device.h>
 #include <linux/mutex.h>
 #include <linux/mfd/syscon.h>
-#include <linux/mfd/syscon/exynos5-pmu.h>
 #include <linux/regmap.h>
 #include <linux/regulator/consumer.h>
+#include <linux/soc/samsung/exynos-regs-pmu.h>
 
 /* Exynos USB PHY registers */
 #define EXYNOS5_FSEL_9MHZ6		0x0
@@ -235,10 +235,10 @@ static void exynos5_usbdrd_phy_isol(struct phy_usb_instance *inst,
 	if (!inst->reg_pmu)
 		return;
 
-	val = on ? 0 : EXYNOS5_PHY_ENABLE;
+	val = on ? 0 : EXYNOS4_PHY_ENABLE;
 
 	regmap_update_bits(inst->reg_pmu, inst->pmu_offset,
-			   EXYNOS5_PHY_ENABLE, val);
+			   EXYNOS4_PHY_ENABLE, val);
 }
 
 /*

+ 3 - 3
drivers/phy/phy-meson8b-usb2.c

@@ -81,9 +81,9 @@
 	#define REG_ADP_BC_ACA_PIN_GND			BIT(25)
 	#define REG_ADP_BC_ACA_PIN_FLOAT		BIT(26)
 
-#define REG_DBG_UART					0x14
+#define REG_DBG_UART					0x10
 
-#define REG_TEST					0x18
+#define REG_TEST					0x14
 	#define REG_TEST_DATA_IN_MASK			GENMASK(3, 0)
 	#define REG_TEST_EN_MASK			GENMASK(7, 4)
 	#define REG_TEST_ADDR_MASK			GENMASK(11, 8)
@@ -93,7 +93,7 @@
 	#define REG_TEST_DATA_OUT_MASK			GENMASK(19, 16)
 	#define REG_TEST_DISABLE_ID_PULLUP		BIT(20)
 
-#define REG_TUNE					0x1c
+#define REG_TUNE					0x18
 	#define REG_TUNE_TX_RES_TUNE_MASK		GENMASK(1, 0)
 	#define REG_TUNE_TX_HSXV_TUNE_MASK		GENMASK(3, 2)
 	#define REG_TUNE_TX_VREF_TUNE_MASK		GENMASK(7, 4)

+ 307 - 170
drivers/phy/phy-mt65xx-usb3.c

@@ -23,47 +23,55 @@
 #include <linux/phy/phy.h>
 #include <linux/platform_device.h>
 
-/*
- * for sifslv2 register, but exclude port's;
- * relative to USB3_SIF2_BASE base address
- */
-#define SSUSB_SIFSLV_SPLLC		0x0000
-#define SSUSB_SIFSLV_U2FREQ		0x0100
-
-/* offsets of sub-segment in each port registers */
-#define SSUSB_SIFSLV_U2PHY_COM_BASE	0x0000
-#define SSUSB_SIFSLV_U3PHYD_BASE	0x0100
-#define SSUSB_USB30_PHYA_SIV_B_BASE	0x0300
-#define SSUSB_SIFSLV_U3PHYA_DA_BASE	0x0400
-
-#define U3P_USBPHYACR0		(SSUSB_SIFSLV_U2PHY_COM_BASE + 0x0000)
+/* version V1 sub-banks offset base address */
+/* banks shared by multiple phys */
+#define SSUSB_SIFSLV_V1_SPLLC		0x000	/* shared by u3 phys */
+#define SSUSB_SIFSLV_V1_U2FREQ		0x100	/* shared by u2 phys */
+/* u2 phy bank */
+#define SSUSB_SIFSLV_V1_U2PHY_COM	0x000
+/* u3 phy banks */
+#define SSUSB_SIFSLV_V1_U3PHYD		0x000
+#define SSUSB_SIFSLV_V1_U3PHYA		0x200
+
+/* version V2 sub-banks offset base address */
+/* u2 phy banks */
+#define SSUSB_SIFSLV_V2_MISC		0x000
+#define SSUSB_SIFSLV_V2_U2FREQ		0x100
+#define SSUSB_SIFSLV_V2_U2PHY_COM	0x300
+/* u3 phy banks */
+#define SSUSB_SIFSLV_V2_SPLLC		0x000
+#define SSUSB_SIFSLV_V2_CHIP		0x100
+#define SSUSB_SIFSLV_V2_U3PHYD		0x200
+#define SSUSB_SIFSLV_V2_U3PHYA		0x400
+
+#define U3P_USBPHYACR0		0x000
 #define PA0_RG_U2PLL_FORCE_ON		BIT(15)
+#define PA0_RG_USB20_INTR_EN		BIT(5)
 
-#define U3P_USBPHYACR2		(SSUSB_SIFSLV_U2PHY_COM_BASE + 0x0008)
+#define U3P_USBPHYACR2		0x008
 #define PA2_RG_SIF_U2PLL_FORCE_EN	BIT(18)
 
-#define U3P_USBPHYACR5		(SSUSB_SIFSLV_U2PHY_COM_BASE + 0x0014)
+#define U3P_USBPHYACR5		0x014
 #define PA5_RG_U2_HSTX_SRCAL_EN	BIT(15)
 #define PA5_RG_U2_HSTX_SRCTRL		GENMASK(14, 12)
 #define PA5_RG_U2_HSTX_SRCTRL_VAL(x)	((0x7 & (x)) << 12)
 #define PA5_RG_U2_HS_100U_U3_EN	BIT(11)
 
-#define U3P_USBPHYACR6		(SSUSB_SIFSLV_U2PHY_COM_BASE + 0x0018)
-#define PA6_RG_U2_ISO_EN		BIT(31)
+#define U3P_USBPHYACR6		0x018
 #define PA6_RG_U2_BC11_SW_EN		BIT(23)
 #define PA6_RG_U2_OTG_VBUSCMP_EN	BIT(20)
 #define PA6_RG_U2_SQTH		GENMASK(3, 0)
 #define PA6_RG_U2_SQTH_VAL(x)	(0xf & (x))
 
-#define U3P_U2PHYACR4		(SSUSB_SIFSLV_U2PHY_COM_BASE + 0x0020)
+#define U3P_U2PHYACR4		0x020
 #define P2C_RG_USB20_GPIO_CTL		BIT(9)
 #define P2C_USB20_GPIO_MODE		BIT(8)
 #define P2C_U2_GPIO_CTR_MSK	(P2C_RG_USB20_GPIO_CTL | P2C_USB20_GPIO_MODE)
 
-#define U3D_U2PHYDCR0		(SSUSB_SIFSLV_U2PHY_COM_BASE + 0x0060)
+#define U3D_U2PHYDCR0		0x060
 #define P2C_RG_SIF_U2PLL_FORCE_ON	BIT(24)
 
-#define U3P_U2PHYDTM0		(SSUSB_SIFSLV_U2PHY_COM_BASE + 0x0068)
+#define U3P_U2PHYDTM0		0x068
 #define P2C_FORCE_UART_EN		BIT(26)
 #define P2C_FORCE_DATAIN		BIT(23)
 #define P2C_FORCE_DM_PULLDOWN		BIT(21)
@@ -85,47 +93,56 @@
 		P2C_FORCE_TERMSEL | P2C_RG_DMPULLDOWN | \
 		P2C_RG_DPPULLDOWN | P2C_RG_TERMSEL)
 
-#define U3P_U2PHYDTM1		(SSUSB_SIFSLV_U2PHY_COM_BASE + 0x006C)
+#define U3P_U2PHYDTM1		0x06C
 #define P2C_RG_UART_EN			BIT(16)
 #define P2C_RG_VBUSVALID		BIT(5)
 #define P2C_RG_SESSEND			BIT(4)
 #define P2C_RG_AVALID			BIT(2)
 
-#define U3P_U3_PHYA_REG0	(SSUSB_USB30_PHYA_SIV_B_BASE + 0x0000)
-#define P3A_RG_U3_VUSB10_ON		BIT(5)
-
-#define U3P_U3_PHYA_REG6	(SSUSB_USB30_PHYA_SIV_B_BASE + 0x0018)
+#define U3P_U3_PHYA_REG6	0x018
 #define P3A_RG_TX_EIDLE_CM		GENMASK(31, 28)
 #define P3A_RG_TX_EIDLE_CM_VAL(x)	((0xf & (x)) << 28)
 
-#define U3P_U3_PHYA_REG9	(SSUSB_USB30_PHYA_SIV_B_BASE + 0x0024)
+#define U3P_U3_PHYA_REG9	0x024
 #define P3A_RG_RX_DAC_MUX		GENMASK(5, 1)
 #define P3A_RG_RX_DAC_MUX_VAL(x)	((0x1f & (x)) << 1)
 
-#define U3P_U3PHYA_DA_REG0	(SSUSB_SIFSLV_U3PHYA_DA_BASE + 0x0000)
+#define U3P_U3_PHYA_DA_REG0	0x100
 #define P3A_RG_XTAL_EXT_EN_U3		GENMASK(11, 10)
 #define P3A_RG_XTAL_EXT_EN_U3_VAL(x)	((0x3 & (x)) << 10)
 
-#define U3P_PHYD_CDR1		(SSUSB_SIFSLV_U3PHYD_BASE + 0x005c)
+#define U3P_U3_PHYD_LFPS1		0x00c
+#define P3D_RG_FWAKE_TH		GENMASK(21, 16)
+#define P3D_RG_FWAKE_TH_VAL(x)	((0x3f & (x)) << 16)
+
+#define U3P_U3_PHYD_CDR1		0x05c
 #define P3D_RG_CDR_BIR_LTD1		GENMASK(28, 24)
 #define P3D_RG_CDR_BIR_LTD1_VAL(x)	((0x1f & (x)) << 24)
 #define P3D_RG_CDR_BIR_LTD0		GENMASK(12, 8)
 #define P3D_RG_CDR_BIR_LTD0_VAL(x)	((0x1f & (x)) << 8)
 
-#define U3P_XTALCTL3		(SSUSB_SIFSLV_SPLLC + 0x0018)
+#define U3P_U3_PHYD_RXDET1		0x128
+#define P3D_RG_RXDET_STB2_SET		GENMASK(17, 9)
+#define P3D_RG_RXDET_STB2_SET_VAL(x)	((0x1ff & (x)) << 9)
+
+#define U3P_U3_PHYD_RXDET2		0x12c
+#define P3D_RG_RXDET_STB2_SET_P3	GENMASK(8, 0)
+#define P3D_RG_RXDET_STB2_SET_P3_VAL(x)	(0x1ff & (x))
+
+#define U3P_SPLLC_XTALCTL3		0x018
 #define XC3_RG_U3_XTAL_RX_PWD		BIT(9)
 #define XC3_RG_U3_FRC_XTAL_RX_PWD	BIT(8)
 
-#define U3P_U2FREQ_FMCR0	(SSUSB_SIFSLV_U2FREQ + 0x00)
+#define U3P_U2FREQ_FMCR0	0x00
 #define P2F_RG_MONCLK_SEL	GENMASK(27, 26)
 #define P2F_RG_MONCLK_SEL_VAL(x)	((0x3 & (x)) << 26)
 #define P2F_RG_FREQDET_EN	BIT(24)
 #define P2F_RG_CYCLECNT		GENMASK(23, 0)
 #define P2F_RG_CYCLECNT_VAL(x)	((P2F_RG_CYCLECNT) & (x))
 
-#define U3P_U2FREQ_VALUE	(SSUSB_SIFSLV_U2FREQ + 0x0c)
+#define U3P_U2FREQ_VALUE	0x0c
 
-#define U3P_U2FREQ_FMMONR1	(SSUSB_SIFSLV_U2FREQ + 0x10)
+#define U3P_U2FREQ_FMMONR1	0x10
 #define P2F_USB_FM_VALID	BIT(0)
 #define P2F_RG_FRCK_EN		BIT(8)
 
@@ -134,21 +151,46 @@
 #define U3P_SR_COEF_DIVISOR	1000
 #define U3P_FM_DET_CYCLE_CNT	1024
 
+enum mt_phy_version {
+	MT_PHY_V1 = 1,
+	MT_PHY_V2,
+};
+
 struct mt65xx_phy_pdata {
 	/* avoid RX sensitivity level degradation only for mt8173 */
 	bool avoid_rx_sen_degradation;
+	enum mt_phy_version version;
+};
+
+struct u2phy_banks {
+	void __iomem *misc;
+	void __iomem *fmreg;
+	void __iomem *com;
+};
+
+struct u3phy_banks {
+	void __iomem *spllc;
+	void __iomem *chip;
+	void __iomem *phyd; /* include u3phyd_bank2 */
+	void __iomem *phya; /* include u3phya_da */
 };
 
 struct mt65xx_phy_instance {
 	struct phy *phy;
 	void __iomem *port_base;
+	union {
+		struct u2phy_banks u2_banks;
+		struct u3phy_banks u3_banks;
+	};
+	struct clk *ref_clk;	/* reference clock of anolog phy */
 	u32 index;
 	u8 type;
 };
 
 struct mt65xx_u3phy {
 	struct device *dev;
-	void __iomem *sif_base;	/* include sif2, but exclude port's */
+	void __iomem *sif_base;	/* only shared sif */
+	/* deprecated, use @ref_clk instead in phy instance */
 	struct clk *u3phya_ref;	/* reference clock of usb3 anolog phy */
 	const struct mt65xx_phy_pdata *pdata;
 	struct mt65xx_phy_instance **phys;
@@ -158,49 +200,53 @@ struct mt65xx_u3phy {
 static void hs_slew_rate_calibrate(struct mt65xx_u3phy *u3phy,
 	struct mt65xx_phy_instance *instance)
 {
-	void __iomem *sif_base = u3phy->sif_base;
+	struct u2phy_banks *u2_banks = &instance->u2_banks;
+	void __iomem *fmreg = u2_banks->fmreg;
+	void __iomem *com = u2_banks->com;
 	int calibration_val;
 	int fm_out;
 	u32 tmp;
 
 	/* enable USB ring oscillator */
-	tmp = readl(instance->port_base + U3P_USBPHYACR5);
+	tmp = readl(com + U3P_USBPHYACR5);
 	tmp |= PA5_RG_U2_HSTX_SRCAL_EN;
-	writel(tmp, instance->port_base + U3P_USBPHYACR5);
+	writel(tmp, com + U3P_USBPHYACR5);
 	udelay(1);
 
 	/*enable free run clock */
-	tmp = readl(sif_base + U3P_U2FREQ_FMMONR1);
+	tmp = readl(fmreg + U3P_U2FREQ_FMMONR1);
 	tmp |= P2F_RG_FRCK_EN;
-	writel(tmp, sif_base + U3P_U2FREQ_FMMONR1);
+	writel(tmp, fmreg + U3P_U2FREQ_FMMONR1);
 
 	/* set cycle count as 1024, and select u2 channel */
-	tmp = readl(sif_base + U3P_U2FREQ_FMCR0);
+	tmp = readl(fmreg + U3P_U2FREQ_FMCR0);
 	tmp &= ~(P2F_RG_CYCLECNT | P2F_RG_MONCLK_SEL);
 	tmp |= P2F_RG_CYCLECNT_VAL(U3P_FM_DET_CYCLE_CNT);
-	tmp |= P2F_RG_MONCLK_SEL_VAL(instance->index);
-	writel(tmp, sif_base + U3P_U2FREQ_FMCR0);
+	if (u3phy->pdata->version == MT_PHY_V1)
+		tmp |= P2F_RG_MONCLK_SEL_VAL(instance->index >> 1);
+
+	writel(tmp, fmreg + U3P_U2FREQ_FMCR0);
 
 	/* enable frequency meter */
-	tmp = readl(sif_base + U3P_U2FREQ_FMCR0);
+	tmp = readl(fmreg + U3P_U2FREQ_FMCR0);
 	tmp |= P2F_RG_FREQDET_EN;
-	writel(tmp, sif_base + U3P_U2FREQ_FMCR0);
+	writel(tmp, fmreg + U3P_U2FREQ_FMCR0);
 
 	/* ignore return value */
-	readl_poll_timeout(sif_base + U3P_U2FREQ_FMMONR1, tmp,
-		  (tmp & P2F_USB_FM_VALID), 10, 200);
+	readl_poll_timeout(fmreg + U3P_U2FREQ_FMMONR1, tmp,
+			   (tmp & P2F_USB_FM_VALID), 10, 200);
 
-	fm_out = readl(sif_base + U3P_U2FREQ_VALUE);
+	fm_out = readl(fmreg + U3P_U2FREQ_VALUE);
 
 	/* disable frequency meter */
-	tmp = readl(sif_base + U3P_U2FREQ_FMCR0);
+	tmp = readl(fmreg + U3P_U2FREQ_FMCR0);
 	tmp &= ~P2F_RG_FREQDET_EN;
-	writel(tmp, sif_base + U3P_U2FREQ_FMCR0);
+	writel(tmp, fmreg + U3P_U2FREQ_FMCR0);
 
 	/*disable free run clock */
-	tmp = readl(sif_base + U3P_U2FREQ_FMMONR1);
+	tmp = readl(fmreg + U3P_U2FREQ_FMMONR1);
 	tmp &= ~P2F_RG_FRCK_EN;
-	writel(tmp, sif_base + U3P_U2FREQ_FMMONR1);
+	writel(tmp, fmreg + U3P_U2FREQ_FMMONR1);
 
 	if (fm_out) {
 		/* ( 1024 / FM_OUT ) x reference clock frequency x 0.028 */
@@ -215,85 +261,125 @@ static void hs_slew_rate_calibrate(struct mt65xx_u3phy *u3phy,
 		instance->index, fm_out, calibration_val);
 
 	/* set HS slew rate */
-	tmp = readl(instance->port_base + U3P_USBPHYACR5);
+	tmp = readl(com + U3P_USBPHYACR5);
 	tmp &= ~PA5_RG_U2_HSTX_SRCTRL;
 	tmp |= PA5_RG_U2_HSTX_SRCTRL_VAL(calibration_val);
-	writel(tmp, instance->port_base + U3P_USBPHYACR5);
+	writel(tmp, com + U3P_USBPHYACR5);
 
 	/* disable USB ring oscillator */
-	tmp = readl(instance->port_base + U3P_USBPHYACR5);
+	tmp = readl(com + U3P_USBPHYACR5);
 	tmp &= ~PA5_RG_U2_HSTX_SRCAL_EN;
-	writel(tmp, instance->port_base + U3P_USBPHYACR5);
+	writel(tmp, com + U3P_USBPHYACR5);
+}
+
+static void u3_phy_instance_init(struct mt65xx_u3phy *u3phy,
+	struct mt65xx_phy_instance *instance)
+{
+	struct u3phy_banks *u3_banks = &instance->u3_banks;
+	u32 tmp;
+
+	/* gating PCIe Analog XTAL clock */
+	tmp = readl(u3_banks->spllc + U3P_SPLLC_XTALCTL3);
+	tmp |= XC3_RG_U3_XTAL_RX_PWD | XC3_RG_U3_FRC_XTAL_RX_PWD;
+	writel(tmp, u3_banks->spllc + U3P_SPLLC_XTALCTL3);
+
+	/* gating XSQ */
+	tmp = readl(u3_banks->phya + U3P_U3_PHYA_DA_REG0);
+	tmp &= ~P3A_RG_XTAL_EXT_EN_U3;
+	tmp |= P3A_RG_XTAL_EXT_EN_U3_VAL(2);
+	writel(tmp, u3_banks->phya + U3P_U3_PHYA_DA_REG0);
+
+	tmp = readl(u3_banks->phya + U3P_U3_PHYA_REG9);
+	tmp &= ~P3A_RG_RX_DAC_MUX;
+	tmp |= P3A_RG_RX_DAC_MUX_VAL(4);
+	writel(tmp, u3_banks->phya + U3P_U3_PHYA_REG9);
+
+	tmp = readl(u3_banks->phya + U3P_U3_PHYA_REG6);
+	tmp &= ~P3A_RG_TX_EIDLE_CM;
+	tmp |= P3A_RG_TX_EIDLE_CM_VAL(0xe);
+	writel(tmp, u3_banks->phya + U3P_U3_PHYA_REG6);
+
+	tmp = readl(u3_banks->phyd + U3P_U3_PHYD_CDR1);
+	tmp &= ~(P3D_RG_CDR_BIR_LTD0 | P3D_RG_CDR_BIR_LTD1);
+	tmp |= P3D_RG_CDR_BIR_LTD0_VAL(0xc) | P3D_RG_CDR_BIR_LTD1_VAL(0x3);
+	writel(tmp, u3_banks->phyd + U3P_U3_PHYD_CDR1);
+
+	tmp = readl(u3_banks->phyd + U3P_U3_PHYD_LFPS1);
+	tmp &= ~P3D_RG_FWAKE_TH;
+	tmp |= P3D_RG_FWAKE_TH_VAL(0x34);
+	writel(tmp, u3_banks->phyd + U3P_U3_PHYD_LFPS1);
+
+	tmp = readl(u3_banks->phyd + U3P_U3_PHYD_RXDET1);
+	tmp &= ~P3D_RG_RXDET_STB2_SET;
+	tmp |= P3D_RG_RXDET_STB2_SET_VAL(0x10);
+	writel(tmp, u3_banks->phyd + U3P_U3_PHYD_RXDET1);
+
+	tmp = readl(u3_banks->phyd + U3P_U3_PHYD_RXDET2);
+	tmp &= ~P3D_RG_RXDET_STB2_SET_P3;
+	tmp |= P3D_RG_RXDET_STB2_SET_P3_VAL(0x10);
+	writel(tmp, u3_banks->phyd + U3P_U3_PHYD_RXDET2);
+
+	dev_dbg(u3phy->dev, "%s(%d)\n", __func__, instance->index);
 }
 
 static void phy_instance_init(struct mt65xx_u3phy *u3phy,
 	struct mt65xx_phy_instance *instance)
 {
-	void __iomem *port_base = instance->port_base;
+	struct u2phy_banks *u2_banks = &instance->u2_banks;
+	void __iomem *com = u2_banks->com;
 	u32 index = instance->index;
 	u32 tmp;
 
 	/* switch to USB function. (system register, force ip into usb mode) */
-	tmp = readl(port_base + U3P_U2PHYDTM0);
+	tmp = readl(com + U3P_U2PHYDTM0);
 	tmp &= ~P2C_FORCE_UART_EN;
 	tmp |= P2C_RG_XCVRSEL_VAL(1) | P2C_RG_DATAIN_VAL(0);
-	writel(tmp, port_base + U3P_U2PHYDTM0);
+	writel(tmp, com + U3P_U2PHYDTM0);
 
-	tmp = readl(port_base + U3P_U2PHYDTM1);
+	tmp = readl(com + U3P_U2PHYDTM1);
 	tmp &= ~P2C_RG_UART_EN;
-	writel(tmp, port_base + U3P_U2PHYDTM1);
+	writel(tmp, com + U3P_U2PHYDTM1);
+
+	tmp = readl(com + U3P_USBPHYACR0);
+	tmp |= PA0_RG_USB20_INTR_EN;
+	writel(tmp, com + U3P_USBPHYACR0);
+
+	/* disable switch 100uA current to SSUSB */
+	tmp = readl(com + U3P_USBPHYACR5);
+	tmp &= ~PA5_RG_U2_HS_100U_U3_EN;
+	writel(tmp, com + U3P_USBPHYACR5);
 
 	if (!index) {
-		tmp = readl(port_base + U3P_U2PHYACR4);
+		tmp = readl(com + U3P_U2PHYACR4);
 		tmp &= ~P2C_U2_GPIO_CTR_MSK;
-		writel(tmp, port_base + U3P_U2PHYACR4);
+		writel(tmp, com + U3P_U2PHYACR4);
 	}
 
 	if (u3phy->pdata->avoid_rx_sen_degradation) {
 		if (!index) {
-			tmp = readl(port_base + U3P_USBPHYACR2);
+			tmp = readl(com + U3P_USBPHYACR2);
 			tmp |= PA2_RG_SIF_U2PLL_FORCE_EN;
-			writel(tmp, port_base + U3P_USBPHYACR2);
+			writel(tmp, com + U3P_USBPHYACR2);
 
-			tmp = readl(port_base + U3D_U2PHYDCR0);
+			tmp = readl(com + U3D_U2PHYDCR0);
 			tmp &= ~P2C_RG_SIF_U2PLL_FORCE_ON;
-			writel(tmp, port_base + U3D_U2PHYDCR0);
+			writel(tmp, com + U3D_U2PHYDCR0);
 		} else {
-			tmp = readl(port_base + U3D_U2PHYDCR0);
+			tmp = readl(com + U3D_U2PHYDCR0);
 			tmp |= P2C_RG_SIF_U2PLL_FORCE_ON;
-			writel(tmp, port_base + U3D_U2PHYDCR0);
+			writel(tmp, com + U3D_U2PHYDCR0);
 
-			tmp = readl(port_base + U3P_U2PHYDTM0);
+			tmp = readl(com + U3P_U2PHYDTM0);
 			tmp |= P2C_RG_SUSPENDM | P2C_FORCE_SUSPENDM;
-			writel(tmp, port_base + U3P_U2PHYDTM0);
+			writel(tmp, com + U3P_U2PHYDTM0);
 		}
 	}
 
-	tmp = readl(port_base + U3P_USBPHYACR6);
+	tmp = readl(com + U3P_USBPHYACR6);
 	tmp &= ~PA6_RG_U2_BC11_SW_EN;	/* DP/DM BC1.1 path Disable */
 	tmp &= ~PA6_RG_U2_SQTH;
 	tmp |= PA6_RG_U2_SQTH_VAL(2);
-	writel(tmp, port_base + U3P_USBPHYACR6);
-
-	tmp = readl(port_base + U3P_U3PHYA_DA_REG0);
-	tmp &= ~P3A_RG_XTAL_EXT_EN_U3;
-	tmp |= P3A_RG_XTAL_EXT_EN_U3_VAL(2);
-	writel(tmp, port_base + U3P_U3PHYA_DA_REG0);
-
-	tmp = readl(port_base + U3P_U3_PHYA_REG9);
-	tmp &= ~P3A_RG_RX_DAC_MUX;
-	tmp |= P3A_RG_RX_DAC_MUX_VAL(4);
-	writel(tmp, port_base + U3P_U3_PHYA_REG9);
-
-	tmp = readl(port_base + U3P_U3_PHYA_REG6);
-	tmp &= ~P3A_RG_TX_EIDLE_CM;
-	tmp |= P3A_RG_TX_EIDLE_CM_VAL(0xe);
-	writel(tmp, port_base + U3P_U3_PHYA_REG6);
-
-	tmp = readl(port_base + U3P_PHYD_CDR1);
-	tmp &= ~(P3D_RG_CDR_BIR_LTD0 | P3D_RG_CDR_BIR_LTD1);
-	tmp |= P3D_RG_CDR_BIR_LTD0_VAL(0xc) | P3D_RG_CDR_BIR_LTD1_VAL(0x3);
-	writel(tmp, port_base + U3P_PHYD_CDR1);
+	writel(tmp, com + U3P_USBPHYACR6);
 
 	dev_dbg(u3phy->dev, "%s(%d)\n", __func__, index);
 }
@@ -301,58 +387,35 @@ static void phy_instance_init(struct mt65xx_u3phy *u3phy,
 static void phy_instance_power_on(struct mt65xx_u3phy *u3phy,
 	struct mt65xx_phy_instance *instance)
 {
-	void __iomem *port_base = instance->port_base;
+	struct u2phy_banks *u2_banks = &instance->u2_banks;
+	void __iomem *com = u2_banks->com;
 	u32 index = instance->index;
 	u32 tmp;
 
-	if (!index) {
-		/* Set RG_SSUSB_VUSB10_ON as 1 after VUSB10 ready */
-		tmp = readl(port_base + U3P_U3_PHYA_REG0);
-		tmp |= P3A_RG_U3_VUSB10_ON;
-		writel(tmp, port_base + U3P_U3_PHYA_REG0);
-	}
-
 	/* (force_suspendm=0) (let suspendm=1, enable usb 480MHz pll) */
-	tmp = readl(port_base + U3P_U2PHYDTM0);
+	tmp = readl(com + U3P_U2PHYDTM0);
 	tmp &= ~(P2C_FORCE_SUSPENDM | P2C_RG_XCVRSEL);
 	tmp &= ~(P2C_RG_DATAIN | P2C_DTM0_PART_MASK);
-	writel(tmp, port_base + U3P_U2PHYDTM0);
+	writel(tmp, com + U3P_U2PHYDTM0);
 
 	/* OTG Enable */
-	tmp = readl(port_base + U3P_USBPHYACR6);
+	tmp = readl(com + U3P_USBPHYACR6);
 	tmp |= PA6_RG_U2_OTG_VBUSCMP_EN;
-	writel(tmp, port_base + U3P_USBPHYACR6);
+	writel(tmp, com + U3P_USBPHYACR6);
 
-	if (!index) {
-		tmp = readl(u3phy->sif_base + U3P_XTALCTL3);
-		tmp |= XC3_RG_U3_XTAL_RX_PWD | XC3_RG_U3_FRC_XTAL_RX_PWD;
-		writel(tmp, u3phy->sif_base + U3P_XTALCTL3);
-
-		/* switch 100uA current to SSUSB */
-		tmp = readl(port_base + U3P_USBPHYACR5);
-		tmp |= PA5_RG_U2_HS_100U_U3_EN;
-		writel(tmp, port_base + U3P_USBPHYACR5);
-	}
-
-	tmp = readl(port_base + U3P_U2PHYDTM1);
+	tmp = readl(com + U3P_U2PHYDTM1);
 	tmp |= P2C_RG_VBUSVALID | P2C_RG_AVALID;
 	tmp &= ~P2C_RG_SESSEND;
-	writel(tmp, port_base + U3P_U2PHYDTM1);
-
-	/* USB 2.0 slew rate calibration */
-	tmp = readl(port_base + U3P_USBPHYACR5);
-	tmp &= ~PA5_RG_U2_HSTX_SRCTRL;
-	tmp |= PA5_RG_U2_HSTX_SRCTRL_VAL(4);
-	writel(tmp, port_base + U3P_USBPHYACR5);
+	writel(tmp, com + U3P_U2PHYDTM1);
 
 	if (u3phy->pdata->avoid_rx_sen_degradation && index) {
-		tmp = readl(port_base + U3D_U2PHYDCR0);
+		tmp = readl(com + U3D_U2PHYDCR0);
 		tmp |= P2C_RG_SIF_U2PLL_FORCE_ON;
-		writel(tmp, port_base + U3D_U2PHYDCR0);
+		writel(tmp, com + U3D_U2PHYDCR0);
 
-		tmp = readl(port_base + U3P_U2PHYDTM0);
+		tmp = readl(com + U3P_U2PHYDTM0);
 		tmp |= P2C_RG_SUSPENDM | P2C_FORCE_SUSPENDM;
-		writel(tmp, port_base + U3P_U2PHYDTM0);
+		writel(tmp, com + U3P_U2PHYDTM0);
 	}
 	dev_dbg(u3phy->dev, "%s(%d)\n", __func__, index);
 }
@@ -360,48 +423,36 @@ static void phy_instance_power_on(struct mt65xx_u3phy *u3phy,
 static void phy_instance_power_off(struct mt65xx_u3phy *u3phy,
 	struct mt65xx_phy_instance *instance)
 {
-	void __iomem *port_base = instance->port_base;
+	struct u2phy_banks *u2_banks = &instance->u2_banks;
+	void __iomem *com = u2_banks->com;
 	u32 index = instance->index;
 	u32 tmp;
 
-	tmp = readl(port_base + U3P_U2PHYDTM0);
+	tmp = readl(com + U3P_U2PHYDTM0);
 	tmp &= ~(P2C_RG_XCVRSEL | P2C_RG_DATAIN);
 	tmp |= P2C_FORCE_SUSPENDM;
-	writel(tmp, port_base + U3P_U2PHYDTM0);
+	writel(tmp, com + U3P_U2PHYDTM0);
 
 	/* OTG Disable */
-	tmp = readl(port_base + U3P_USBPHYACR6);
+	tmp = readl(com + U3P_USBPHYACR6);
 	tmp &= ~PA6_RG_U2_OTG_VBUSCMP_EN;
-	writel(tmp, port_base + U3P_USBPHYACR6);
-
-	if (!index) {
-		/* switch 100uA current back to USB2.0 */
-		tmp = readl(port_base + U3P_USBPHYACR5);
-		tmp &= ~PA5_RG_U2_HS_100U_U3_EN;
-		writel(tmp, port_base + U3P_USBPHYACR5);
-	}
+	writel(tmp, com + U3P_USBPHYACR6);
 
 	/* let suspendm=0, set utmi into analog power down */
-	tmp = readl(port_base + U3P_U2PHYDTM0);
+	tmp = readl(com + U3P_U2PHYDTM0);
 	tmp &= ~P2C_RG_SUSPENDM;
-	writel(tmp, port_base + U3P_U2PHYDTM0);
+	writel(tmp, com + U3P_U2PHYDTM0);
 	udelay(1);
 
-	tmp = readl(port_base + U3P_U2PHYDTM1);
+	tmp = readl(com + U3P_U2PHYDTM1);
 	tmp &= ~(P2C_RG_VBUSVALID | P2C_RG_AVALID);
 	tmp |= P2C_RG_SESSEND;
-	writel(tmp, port_base + U3P_U2PHYDTM1);
-
-	if (!index) {
-		tmp = readl(port_base + U3P_U3_PHYA_REG0);
-		tmp &= ~P3A_RG_U3_VUSB10_ON;
-		writel(tmp, port_base + U3P_U3_PHYA_REG0);
-	}
+	writel(tmp, com + U3P_U2PHYDTM1);
 
 	if (u3phy->pdata->avoid_rx_sen_degradation && index) {
-		tmp = readl(port_base + U3D_U2PHYDCR0);
+		tmp = readl(com + U3D_U2PHYDCR0);
 		tmp &= ~P2C_RG_SIF_U2PLL_FORCE_ON;
-		writel(tmp, port_base + U3D_U2PHYDCR0);
+		writel(tmp, com + U3D_U2PHYDCR0);
 	}
 
 	dev_dbg(u3phy->dev, "%s(%d)\n", __func__, index);
@@ -410,18 +461,55 @@ static void phy_instance_power_off(struct mt65xx_u3phy *u3phy,
 static void phy_instance_exit(struct mt65xx_u3phy *u3phy,
 	struct mt65xx_phy_instance *instance)
 {
-	void __iomem *port_base = instance->port_base;
+	struct u2phy_banks *u2_banks = &instance->u2_banks;
+	void __iomem *com = u2_banks->com;
 	u32 index = instance->index;
 	u32 tmp;
 
 	if (u3phy->pdata->avoid_rx_sen_degradation && index) {
-		tmp = readl(port_base + U3D_U2PHYDCR0);
+		tmp = readl(com + U3D_U2PHYDCR0);
 		tmp &= ~P2C_RG_SIF_U2PLL_FORCE_ON;
-		writel(tmp, port_base + U3D_U2PHYDCR0);
+		writel(tmp, com + U3D_U2PHYDCR0);
 
-		tmp = readl(port_base + U3P_U2PHYDTM0);
+		tmp = readl(com + U3P_U2PHYDTM0);
 		tmp &= ~P2C_FORCE_SUSPENDM;
-		writel(tmp, port_base + U3P_U2PHYDTM0);
+		writel(tmp, com + U3P_U2PHYDTM0);
+	}
+}
+
+static void phy_v1_banks_init(struct mt65xx_u3phy *u3phy,
+			      struct mt65xx_phy_instance *instance)
+{
+	struct u2phy_banks *u2_banks = &instance->u2_banks;
+	struct u3phy_banks *u3_banks = &instance->u3_banks;
+
+	if (instance->type == PHY_TYPE_USB2) {
+		u2_banks->misc = NULL;
+		u2_banks->fmreg = u3phy->sif_base + SSUSB_SIFSLV_V1_U2FREQ;
+		u2_banks->com = instance->port_base + SSUSB_SIFSLV_V1_U2PHY_COM;
+	} else if (instance->type == PHY_TYPE_USB3) {
+		u3_banks->spllc = u3phy->sif_base + SSUSB_SIFSLV_V1_SPLLC;
+		u3_banks->chip = NULL;
+		u3_banks->phyd = instance->port_base + SSUSB_SIFSLV_V1_U3PHYD;
+		u3_banks->phya = instance->port_base + SSUSB_SIFSLV_V1_U3PHYA;
+	}
+}
+
+static void phy_v2_banks_init(struct mt65xx_u3phy *u3phy,
+			      struct mt65xx_phy_instance *instance)
+{
+	struct u2phy_banks *u2_banks = &instance->u2_banks;
+	struct u3phy_banks *u3_banks = &instance->u3_banks;
+
+	if (instance->type == PHY_TYPE_USB2) {
+		u2_banks->misc = instance->port_base + SSUSB_SIFSLV_V2_MISC;
+		u2_banks->fmreg = instance->port_base + SSUSB_SIFSLV_V2_U2FREQ;
+		u2_banks->com = instance->port_base + SSUSB_SIFSLV_V2_U2PHY_COM;
+	} else if (instance->type == PHY_TYPE_USB3) {
+		u3_banks->spllc = instance->port_base + SSUSB_SIFSLV_V2_SPLLC;
+		u3_banks->chip = instance->port_base + SSUSB_SIFSLV_V2_CHIP;
+		u3_banks->phyd = instance->port_base + SSUSB_SIFSLV_V2_U3PHYD;
+		u3_banks->phya = instance->port_base + SSUSB_SIFSLV_V2_U3PHYA;
 	}
 }
 
@@ -437,7 +525,17 @@ static int mt65xx_phy_init(struct phy *phy)
 		return ret;
 	}
 
-	phy_instance_init(u3phy, instance);
+	ret = clk_prepare_enable(instance->ref_clk);
+	if (ret) {
+		dev_err(u3phy->dev, "failed to enable ref_clk\n");
+		return ret;
+	}
+
+	if (instance->type == PHY_TYPE_USB2)
+		phy_instance_init(u3phy, instance);
+	else
+		u3_phy_instance_init(u3phy, instance);
+
 	return 0;
 }
 
@@ -446,8 +544,10 @@ static int mt65xx_phy_power_on(struct phy *phy)
 	struct mt65xx_phy_instance *instance = phy_get_drvdata(phy);
 	struct mt65xx_u3phy *u3phy = dev_get_drvdata(phy->dev.parent);
 
-	phy_instance_power_on(u3phy, instance);
-	hs_slew_rate_calibrate(u3phy, instance);
+	if (instance->type == PHY_TYPE_USB2) {
+		phy_instance_power_on(u3phy, instance);
+		hs_slew_rate_calibrate(u3phy, instance);
+	}
 	return 0;
 }
 
@@ -456,7 +556,9 @@ static int mt65xx_phy_power_off(struct phy *phy)
 	struct mt65xx_phy_instance *instance = phy_get_drvdata(phy);
 	struct mt65xx_u3phy *u3phy = dev_get_drvdata(phy->dev.parent);
 
-	phy_instance_power_off(u3phy, instance);
+	if (instance->type == PHY_TYPE_USB2)
+		phy_instance_power_off(u3phy, instance);
+
 	return 0;
 }
 
@@ -465,7 +567,10 @@ static int mt65xx_phy_exit(struct phy *phy)
 	struct mt65xx_phy_instance *instance = phy_get_drvdata(phy);
 	struct mt65xx_u3phy *u3phy = dev_get_drvdata(phy->dev.parent);
 
-	phy_instance_exit(u3phy, instance);
+	if (instance->type == PHY_TYPE_USB2)
+		phy_instance_exit(u3phy, instance);
+
+	clk_disable_unprepare(instance->ref_clk);
 	clk_disable_unprepare(u3phy->u3phya_ref);
 	return 0;
 }
@@ -478,7 +583,6 @@ static struct phy *mt65xx_phy_xlate(struct device *dev,
 	struct device_node *phy_np = args->np;
 	int index;
 
-
 	if (args->args_count != 1) {
 		dev_err(dev, "invalid number of cells in 'phy' property\n");
 		return ERR_PTR(-EINVAL);
@@ -496,13 +600,21 @@ static struct phy *mt65xx_phy_xlate(struct device *dev,
 	}
 
 	instance->type = args->args[0];
-
 	if (!(instance->type == PHY_TYPE_USB2 ||
 	      instance->type == PHY_TYPE_USB3)) {
 		dev_err(dev, "unsupported device type: %d\n", instance->type);
 		return ERR_PTR(-EINVAL);
 	}
 
+	if (u3phy->pdata->version == MT_PHY_V1) {
+		phy_v1_banks_init(u3phy, instance);
+	} else if (u3phy->pdata->version == MT_PHY_V2) {
+		phy_v2_banks_init(u3phy, instance);
+	} else {
+		dev_err(dev, "phy version is not supported\n");
+		return ERR_PTR(-EINVAL);
+	}
+
 	return instance->phy;
 }
 
@@ -516,14 +628,22 @@ static const struct phy_ops mt65xx_u3phy_ops = {
 
 static const struct mt65xx_phy_pdata mt2701_pdata = {
 	.avoid_rx_sen_degradation = false,
+	.version = MT_PHY_V1,
+};
+
+static const struct mt65xx_phy_pdata mt2712_pdata = {
+	.avoid_rx_sen_degradation = false,
+	.version = MT_PHY_V2,
 };
 
 static const struct mt65xx_phy_pdata mt8173_pdata = {
 	.avoid_rx_sen_degradation = true,
+	.version = MT_PHY_V1,
 };
 
 static const struct of_device_id mt65xx_u3phy_id_table[] = {
 	{ .compatible = "mediatek,mt2701-u3phy", .data = &mt2701_pdata },
+	{ .compatible = "mediatek,mt2712-u3phy", .data = &mt2712_pdata },
 	{ .compatible = "mediatek,mt8173-u3phy", .data = &mt8173_pdata },
 	{ },
 };
@@ -559,17 +679,23 @@ static int mt65xx_u3phy_probe(struct platform_device *pdev)
 	u3phy->dev = dev;
 	platform_set_drvdata(pdev, u3phy);
 
-	sif_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-	u3phy->sif_base = devm_ioremap_resource(dev, sif_res);
-	if (IS_ERR(u3phy->sif_base)) {
-		dev_err(dev, "failed to remap sif regs\n");
-		return PTR_ERR(u3phy->sif_base);
+	if (u3phy->pdata->version == MT_PHY_V1) {
+		/* get banks shared by multiple phys */
+		sif_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+		u3phy->sif_base = devm_ioremap_resource(dev, sif_res);
+		if (IS_ERR(u3phy->sif_base)) {
+			dev_err(dev, "failed to remap sif regs\n");
+			return PTR_ERR(u3phy->sif_base);
+		}
 	}
 
+	/* it's deprecated, make it optional for backward compatibility */
 	u3phy->u3phya_ref = devm_clk_get(dev, "u3phya_ref");
 	if (IS_ERR(u3phy->u3phya_ref)) {
-		dev_err(dev, "error to get u3phya_ref\n");
-		return PTR_ERR(u3phy->u3phya_ref);
+		if (PTR_ERR(u3phy->u3phya_ref) == -EPROBE_DEFER)
+			return -EPROBE_DEFER;
+
+		u3phy->u3phya_ref = NULL;
 	}
 
 	port = 0;
@@ -610,6 +736,17 @@ static int mt65xx_u3phy_probe(struct platform_device *pdev)
 		instance->index = port;
 		phy_set_drvdata(phy, instance);
 		port++;
+
+		/* if deprecated clock is provided, ignore instance's one */
+		if (u3phy->u3phya_ref)
+			continue;
+
+		instance->ref_clk = devm_clk_get(&phy->dev, "ref");
+		if (IS_ERR(instance->ref_clk)) {
+			dev_err(dev, "failed to get ref_clk(id-%d)\n", port);
+			retval = PTR_ERR(instance->ref_clk);
+			goto put_child;
+		}
 	}
 
 	provider = devm_of_phy_provider_register(dev, mt65xx_phy_xlate);

+ 1153 - 0
drivers/phy/phy-qcom-qmp.c

@@ -0,0 +1,1153 @@
+/*
+ * Copyright (c) 2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_address.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 <dt-bindings/phy/phy.h>
+
+/* QMP PHY QSERDES COM registers */
+#define QSERDES_COM_BG_TIMER				0x00c
+#define QSERDES_COM_SSC_EN_CENTER			0x010
+#define QSERDES_COM_SSC_ADJ_PER1			0x014
+#define QSERDES_COM_SSC_ADJ_PER2			0x018
+#define QSERDES_COM_SSC_PER1				0x01c
+#define QSERDES_COM_SSC_PER2				0x020
+#define QSERDES_COM_SSC_STEP_SIZE1			0x024
+#define QSERDES_COM_SSC_STEP_SIZE2			0x028
+#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN			0x034
+#define QSERDES_COM_CLK_ENABLE1				0x038
+#define QSERDES_COM_SYS_CLK_CTRL			0x03c
+#define QSERDES_COM_SYSCLK_BUF_ENABLE			0x040
+#define QSERDES_COM_PLL_IVCO				0x048
+#define QSERDES_COM_LOCK_CMP1_MODE0			0x04c
+#define QSERDES_COM_LOCK_CMP2_MODE0			0x050
+#define QSERDES_COM_LOCK_CMP3_MODE0			0x054
+#define QSERDES_COM_LOCK_CMP1_MODE1			0x058
+#define QSERDES_COM_LOCK_CMP2_MODE1			0x05c
+#define QSERDES_COM_LOCK_CMP3_MODE1			0x060
+#define QSERDES_COM_BG_TRIM				0x070
+#define QSERDES_COM_CLK_EP_DIV				0x074
+#define QSERDES_COM_CP_CTRL_MODE0			0x078
+#define QSERDES_COM_CP_CTRL_MODE1			0x07c
+#define QSERDES_COM_PLL_RCTRL_MODE0			0x084
+#define QSERDES_COM_PLL_RCTRL_MODE1			0x088
+#define QSERDES_COM_PLL_CCTRL_MODE0			0x090
+#define QSERDES_COM_PLL_CCTRL_MODE1			0x094
+#define QSERDES_COM_SYSCLK_EN_SEL			0x0ac
+#define QSERDES_COM_RESETSM_CNTRL			0x0b4
+#define QSERDES_COM_RESTRIM_CTRL			0x0bc
+#define QSERDES_COM_RESCODE_DIV_NUM			0x0c4
+#define QSERDES_COM_LOCK_CMP_EN				0x0c8
+#define QSERDES_COM_LOCK_CMP_CFG			0x0cc
+#define QSERDES_COM_DEC_START_MODE0			0x0d0
+#define QSERDES_COM_DEC_START_MODE1			0x0d4
+#define QSERDES_COM_DIV_FRAC_START1_MODE0		0x0dc
+#define QSERDES_COM_DIV_FRAC_START2_MODE0		0x0e0
+#define QSERDES_COM_DIV_FRAC_START3_MODE0		0x0e4
+#define QSERDES_COM_DIV_FRAC_START1_MODE1		0x0e8
+#define QSERDES_COM_DIV_FRAC_START2_MODE1		0x0ec
+#define QSERDES_COM_DIV_FRAC_START3_MODE1		0x0f0
+#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0		0x108
+#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0		0x10c
+#define QSERDES_COM_INTEGLOOP_GAIN0_MODE1		0x110
+#define QSERDES_COM_INTEGLOOP_GAIN1_MODE1		0x114
+#define QSERDES_COM_VCO_TUNE_CTRL			0x124
+#define QSERDES_COM_VCO_TUNE_MAP			0x128
+#define QSERDES_COM_VCO_TUNE1_MODE0			0x12c
+#define QSERDES_COM_VCO_TUNE2_MODE0			0x130
+#define QSERDES_COM_VCO_TUNE1_MODE1			0x134
+#define QSERDES_COM_VCO_TUNE2_MODE1			0x138
+#define QSERDES_COM_VCO_TUNE_TIMER1			0x144
+#define QSERDES_COM_VCO_TUNE_TIMER2			0x148
+#define QSERDES_COM_BG_CTRL				0x170
+#define QSERDES_COM_CLK_SELECT				0x174
+#define QSERDES_COM_HSCLK_SEL				0x178
+#define QSERDES_COM_CORECLK_DIV				0x184
+#define QSERDES_COM_CORE_CLK_EN				0x18c
+#define QSERDES_COM_C_READY_STATUS			0x190
+#define QSERDES_COM_CMN_CONFIG				0x194
+#define QSERDES_COM_SVS_MODE_CLK_SEL			0x19c
+#define QSERDES_COM_DEBUG_BUS0				0x1a0
+#define QSERDES_COM_DEBUG_BUS1				0x1a4
+#define QSERDES_COM_DEBUG_BUS2				0x1a8
+#define QSERDES_COM_DEBUG_BUS3				0x1ac
+#define QSERDES_COM_DEBUG_BUS_SEL			0x1b0
+#define QSERDES_COM_CORECLK_DIV_MODE1			0x1bc
+
+/* QMP PHY TX registers */
+#define QSERDES_TX_RES_CODE_LANE_OFFSET			0x054
+#define QSERDES_TX_DEBUG_BUS_SEL			0x064
+#define QSERDES_TX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN	0x068
+#define QSERDES_TX_LANE_MODE				0x094
+#define QSERDES_TX_RCV_DETECT_LVL_2			0x0ac
+
+/* QMP PHY RX registers */
+#define QSERDES_RX_UCDR_SO_GAIN_HALF			0x010
+#define QSERDES_RX_UCDR_SO_GAIN				0x01c
+#define QSERDES_RX_UCDR_FASTLOCK_FO_GAIN		0x040
+#define QSERDES_RX_UCDR_SO_SATURATION_AND_ENABLE	0x048
+#define QSERDES_RX_RX_TERM_BW				0x090
+#define QSERDES_RX_RX_EQ_GAIN1_LSB			0x0c4
+#define QSERDES_RX_RX_EQ_GAIN1_MSB			0x0c8
+#define QSERDES_RX_RX_EQ_GAIN2_LSB			0x0cc
+#define QSERDES_RX_RX_EQ_GAIN2_MSB			0x0d0
+#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2		0x0d8
+#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL3		0x0dc
+#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL4		0x0e0
+#define QSERDES_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1		0x108
+#define QSERDES_RX_RX_OFFSET_ADAPTOR_CNTRL2		0x10c
+#define QSERDES_RX_SIGDET_ENABLES			0x110
+#define QSERDES_RX_SIGDET_CNTRL				0x114
+#define QSERDES_RX_SIGDET_LVL				0x118
+#define QSERDES_RX_SIGDET_DEGLITCH_CNTRL		0x11c
+#define QSERDES_RX_RX_BAND				0x120
+#define QSERDES_RX_RX_INTERFACE_MODE			0x12c
+
+/* QMP PHY PCS registers */
+#define QPHY_POWER_DOWN_CONTROL				0x04
+#define QPHY_TXDEEMPH_M6DB_V0				0x24
+#define QPHY_TXDEEMPH_M3P5DB_V0				0x28
+#define QPHY_ENDPOINT_REFCLK_DRIVE			0x54
+#define QPHY_RX_IDLE_DTCT_CNTRL				0x58
+#define QPHY_POWER_STATE_CONFIG1			0x60
+#define QPHY_POWER_STATE_CONFIG2			0x64
+#define QPHY_POWER_STATE_CONFIG4			0x6c
+#define QPHY_LOCK_DETECT_CONFIG1			0x80
+#define QPHY_LOCK_DETECT_CONFIG2			0x84
+#define QPHY_LOCK_DETECT_CONFIG3			0x88
+#define QPHY_PWRUP_RESET_DLY_TIME_AUXCLK		0xa0
+#define QPHY_LP_WAKEUP_DLY_TIME_AUXCLK			0xa4
+
+/* QPHY_SW_RESET bit */
+#define SW_RESET				BIT(0)
+/* QPHY_POWER_DOWN_CONTROL */
+#define SW_PWRDN				BIT(0)
+#define REFCLK_DRV_DSBL				BIT(1)
+/* QPHY_START_CONTROL bits */
+#define SERDES_START				BIT(0)
+#define PCS_START				BIT(1)
+#define PLL_READY_GATE_EN			BIT(3)
+/* QPHY_PCS_STATUS bit */
+#define PHYSTATUS				BIT(6)
+/* QPHY_COM_PCS_READY_STATUS bit */
+#define PCS_READY				BIT(0)
+
+#define PHY_INIT_COMPLETE_TIMEOUT		1000
+#define POWER_DOWN_DELAY_US_MIN			10
+#define POWER_DOWN_DELAY_US_MAX			11
+
+#define MAX_PROP_NAME				32
+
+struct qmp_phy_init_tbl {
+	unsigned int offset;
+	unsigned int val;
+	/*
+	 * register part of layout ?
+	 * if yes, then offset gives index in the reg-layout
+	 */
+	int in_layout;
+};
+
+#define QMP_PHY_INIT_CFG(o, v)		\
+	{				\
+		.offset = o,		\
+		.val = v,		\
+	}
+
+#define QMP_PHY_INIT_CFG_L(o, v)	\
+	{				\
+		.offset = o,		\
+		.val = v,		\
+		.in_layout = 1,		\
+	}
+
+/* set of registers with offsets different per-PHY */
+enum qphy_reg_layout {
+	/* Common block control registers */
+	QPHY_COM_SW_RESET,
+	QPHY_COM_POWER_DOWN_CONTROL,
+	QPHY_COM_START_CONTROL,
+	QPHY_COM_PCS_READY_STATUS,
+	/* PCS registers */
+	QPHY_PLL_LOCK_CHK_DLY_TIME,
+	QPHY_FLL_CNTRL1,
+	QPHY_FLL_CNTRL2,
+	QPHY_FLL_CNT_VAL_L,
+	QPHY_FLL_CNT_VAL_H_TOL,
+	QPHY_FLL_MAN_CODE,
+	QPHY_SW_RESET,
+	QPHY_START_CTRL,
+	QPHY_PCS_READY_STATUS,
+};
+
+static const unsigned int pciephy_regs_layout[] = {
+	[QPHY_COM_SW_RESET]		= 0x400,
+	[QPHY_COM_POWER_DOWN_CONTROL]	= 0x404,
+	[QPHY_COM_START_CONTROL]	= 0x408,
+	[QPHY_COM_PCS_READY_STATUS]	= 0x448,
+	[QPHY_PLL_LOCK_CHK_DLY_TIME]	= 0xa8,
+	[QPHY_FLL_CNTRL1]		= 0xc4,
+	[QPHY_FLL_CNTRL2]		= 0xc8,
+	[QPHY_FLL_CNT_VAL_L]		= 0xcc,
+	[QPHY_FLL_CNT_VAL_H_TOL]	= 0xd0,
+	[QPHY_FLL_MAN_CODE]		= 0xd4,
+	[QPHY_SW_RESET]			= 0x00,
+	[QPHY_START_CTRL]		= 0x08,
+	[QPHY_PCS_READY_STATUS]		= 0x174,
+};
+
+static const unsigned int usb3phy_regs_layout[] = {
+	[QPHY_FLL_CNTRL1]		= 0xc0,
+	[QPHY_FLL_CNTRL2]		= 0xc4,
+	[QPHY_FLL_CNT_VAL_L]		= 0xc8,
+	[QPHY_FLL_CNT_VAL_H_TOL]	= 0xcc,
+	[QPHY_FLL_MAN_CODE]		= 0xd0,
+	[QPHY_SW_RESET]			= 0x00,
+	[QPHY_START_CTRL]		= 0x08,
+	[QPHY_PCS_READY_STATUS]		= 0x17c,
+};
+
+static const struct qmp_phy_init_tbl msm8996_pcie_serdes_tbl[] = {
+	QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x1c),
+	QMP_PHY_INIT_CFG(QSERDES_COM_CLK_ENABLE1, 0x10),
+	QMP_PHY_INIT_CFG(QSERDES_COM_CLK_SELECT, 0x33),
+	QMP_PHY_INIT_CFG(QSERDES_COM_CMN_CONFIG, 0x06),
+	QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP_EN, 0x42),
+	QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_MAP, 0x00),
+	QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_TIMER1, 0xff),
+	QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_TIMER2, 0x1f),
+	QMP_PHY_INIT_CFG(QSERDES_COM_HSCLK_SEL, 0x01),
+	QMP_PHY_INIT_CFG(QSERDES_COM_SVS_MODE_CLK_SEL, 0x01),
+	QMP_PHY_INIT_CFG(QSERDES_COM_CORE_CLK_EN, 0x00),
+	QMP_PHY_INIT_CFG(QSERDES_COM_CORECLK_DIV, 0x0a),
+	QMP_PHY_INIT_CFG(QSERDES_COM_BG_TIMER, 0x09),
+	QMP_PHY_INIT_CFG(QSERDES_COM_DEC_START_MODE0, 0x82),
+	QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x03),
+	QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x55),
+	QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x55),
+	QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP3_MODE0, 0x00),
+	QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP2_MODE0, 0x1a),
+	QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP1_MODE0, 0x0a),
+	QMP_PHY_INIT_CFG(QSERDES_COM_CLK_SELECT, 0x33),
+	QMP_PHY_INIT_CFG(QSERDES_COM_SYS_CLK_CTRL, 0x02),
+	QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_BUF_ENABLE, 0x1f),
+	QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_EN_SEL, 0x04),
+	QMP_PHY_INIT_CFG(QSERDES_COM_CP_CTRL_MODE0, 0x0b),
+	QMP_PHY_INIT_CFG(QSERDES_COM_PLL_RCTRL_MODE0, 0x16),
+	QMP_PHY_INIT_CFG(QSERDES_COM_PLL_CCTRL_MODE0, 0x28),
+	QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x00),
+	QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80),
+	QMP_PHY_INIT_CFG(QSERDES_COM_SSC_EN_CENTER, 0x01),
+	QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER1, 0x31),
+	QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER2, 0x01),
+	QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER1, 0x02),
+	QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER2, 0x00),
+	QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE1, 0x2f),
+	QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE2, 0x19),
+	QMP_PHY_INIT_CFG(QSERDES_COM_RESCODE_DIV_NUM, 0x15),
+	QMP_PHY_INIT_CFG(QSERDES_COM_BG_TRIM, 0x0f),
+	QMP_PHY_INIT_CFG(QSERDES_COM_PLL_IVCO, 0x0f),
+	QMP_PHY_INIT_CFG(QSERDES_COM_CLK_EP_DIV, 0x19),
+	QMP_PHY_INIT_CFG(QSERDES_COM_CLK_ENABLE1, 0x10),
+	QMP_PHY_INIT_CFG(QSERDES_COM_HSCLK_SEL, 0x00),
+	QMP_PHY_INIT_CFG(QSERDES_COM_RESCODE_DIV_NUM, 0x40),
+};
+
+static const struct qmp_phy_init_tbl msm8996_pcie_tx_tbl[] = {
+	QMP_PHY_INIT_CFG(QSERDES_TX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, 0x45),
+	QMP_PHY_INIT_CFG(QSERDES_TX_LANE_MODE, 0x06),
+};
+
+static const struct qmp_phy_init_tbl msm8996_pcie_rx_tbl[] = {
+	QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_ENABLES, 0x1c),
+	QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x01),
+	QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL3, 0x00),
+	QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL4, 0xdb),
+	QMP_PHY_INIT_CFG(QSERDES_RX_RX_BAND, 0x18),
+	QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_GAIN, 0x04),
+	QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_GAIN_HALF, 0x04),
+	QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_SATURATION_AND_ENABLE, 0x4b),
+	QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x14),
+	QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_LVL, 0x19),
+};
+
+static const struct qmp_phy_init_tbl msm8996_pcie_pcs_tbl[] = {
+	QMP_PHY_INIT_CFG(QPHY_RX_IDLE_DTCT_CNTRL, 0x4c),
+	QMP_PHY_INIT_CFG(QPHY_PWRUP_RESET_DLY_TIME_AUXCLK, 0x00),
+	QMP_PHY_INIT_CFG(QPHY_LP_WAKEUP_DLY_TIME_AUXCLK, 0x01),
+
+	QMP_PHY_INIT_CFG_L(QPHY_PLL_LOCK_CHK_DLY_TIME, 0x05),
+
+	QMP_PHY_INIT_CFG(QPHY_ENDPOINT_REFCLK_DRIVE, 0x05),
+	QMP_PHY_INIT_CFG(QPHY_POWER_DOWN_CONTROL, 0x02),
+	QMP_PHY_INIT_CFG(QPHY_POWER_STATE_CONFIG4, 0x00),
+	QMP_PHY_INIT_CFG(QPHY_POWER_STATE_CONFIG1, 0xa3),
+	QMP_PHY_INIT_CFG(QPHY_TXDEEMPH_M3P5DB_V0, 0x0e),
+};
+
+static const struct qmp_phy_init_tbl msm8996_usb3_serdes_tbl[] = {
+	QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_EN_SEL, 0x14),
+	QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x08),
+	QMP_PHY_INIT_CFG(QSERDES_COM_CLK_SELECT, 0x30),
+	QMP_PHY_INIT_CFG(QSERDES_COM_CMN_CONFIG, 0x06),
+	QMP_PHY_INIT_CFG(QSERDES_COM_SVS_MODE_CLK_SEL, 0x01),
+	QMP_PHY_INIT_CFG(QSERDES_COM_HSCLK_SEL, 0x00),
+	QMP_PHY_INIT_CFG(QSERDES_COM_BG_TRIM, 0x0f),
+	QMP_PHY_INIT_CFG(QSERDES_COM_PLL_IVCO, 0x0f),
+	QMP_PHY_INIT_CFG(QSERDES_COM_SYS_CLK_CTRL, 0x04),
+	/* PLL and Loop filter settings */
+	QMP_PHY_INIT_CFG(QSERDES_COM_DEC_START_MODE0, 0x82),
+	QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x55),
+	QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x55),
+	QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x03),
+	QMP_PHY_INIT_CFG(QSERDES_COM_CP_CTRL_MODE0, 0x0b),
+	QMP_PHY_INIT_CFG(QSERDES_COM_PLL_RCTRL_MODE0, 0x16),
+	QMP_PHY_INIT_CFG(QSERDES_COM_PLL_CCTRL_MODE0, 0x28),
+	QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80),
+	QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_CTRL, 0x00),
+	QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP1_MODE0, 0x15),
+	QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP2_MODE0, 0x34),
+	QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP3_MODE0, 0x00),
+	QMP_PHY_INIT_CFG(QSERDES_COM_CORE_CLK_EN, 0x00),
+	QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP_CFG, 0x00),
+	QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_MAP, 0x00),
+	QMP_PHY_INIT_CFG(QSERDES_COM_BG_TIMER, 0x0a),
+	/* SSC settings */
+	QMP_PHY_INIT_CFG(QSERDES_COM_SSC_EN_CENTER, 0x01),
+	QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER1, 0x31),
+	QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER2, 0x01),
+	QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER1, 0x00),
+	QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER2, 0x00),
+	QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE1, 0xde),
+	QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE2, 0x07),
+};
+
+static const struct qmp_phy_init_tbl msm8996_usb3_tx_tbl[] = {
+	QMP_PHY_INIT_CFG(QSERDES_TX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, 0x45),
+	QMP_PHY_INIT_CFG(QSERDES_TX_RCV_DETECT_LVL_2, 0x12),
+	QMP_PHY_INIT_CFG(QSERDES_TX_LANE_MODE, 0x06),
+};
+
+static const struct qmp_phy_init_tbl msm8996_usb3_rx_tbl[] = {
+	QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_FASTLOCK_FO_GAIN, 0x0b),
+	QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_GAIN, 0x04),
+	QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x02),
+	QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL3, 0x4c),
+	QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL4, 0xbb),
+	QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1, 0x77),
+	QMP_PHY_INIT_CFG(QSERDES_RX_RX_OFFSET_ADAPTOR_CNTRL2, 0x80),
+	QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_CNTRL, 0x03),
+	QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_LVL, 0x18),
+	QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x16),
+};
+
+static const struct qmp_phy_init_tbl msm8996_usb3_pcs_tbl[] = {
+	/* FLL settings */
+	QMP_PHY_INIT_CFG_L(QPHY_FLL_CNTRL2, 0x03),
+	QMP_PHY_INIT_CFG_L(QPHY_FLL_CNTRL1, 0x02),
+	QMP_PHY_INIT_CFG_L(QPHY_FLL_CNT_VAL_L, 0x09),
+	QMP_PHY_INIT_CFG_L(QPHY_FLL_CNT_VAL_H_TOL, 0x42),
+	QMP_PHY_INIT_CFG_L(QPHY_FLL_MAN_CODE, 0x85),
+
+	/* Lock Det settings */
+	QMP_PHY_INIT_CFG(QPHY_LOCK_DETECT_CONFIG1, 0xd1),
+	QMP_PHY_INIT_CFG(QPHY_LOCK_DETECT_CONFIG2, 0x1f),
+	QMP_PHY_INIT_CFG(QPHY_LOCK_DETECT_CONFIG3, 0x47),
+	QMP_PHY_INIT_CFG(QPHY_POWER_STATE_CONFIG2, 0x08),
+};
+
+/* struct qmp_phy_cfg - per-PHY initialization config */
+struct qmp_phy_cfg {
+	/* phy-type - PCIE/UFS/USB */
+	unsigned int type;
+	/* number of lanes provided by phy */
+	int nlanes;
+
+	/* Init sequence for PHY blocks - serdes, tx, rx, pcs */
+	const struct qmp_phy_init_tbl *serdes_tbl;
+	int serdes_tbl_num;
+	const struct qmp_phy_init_tbl *tx_tbl;
+	int tx_tbl_num;
+	const struct qmp_phy_init_tbl *rx_tbl;
+	int rx_tbl_num;
+	const struct qmp_phy_init_tbl *pcs_tbl;
+	int pcs_tbl_num;
+
+	/* clock ids to be requested */
+	const char * const *clk_list;
+	int num_clks;
+	/* resets to be requested */
+	const char * const *reset_list;
+	int num_resets;
+	/* regulators to be requested */
+	const char * const *vreg_list;
+	int num_vregs;
+
+	/* array of registers with different offsets */
+	const unsigned int *regs;
+
+	unsigned int start_ctrl;
+	unsigned int pwrdn_ctrl;
+	unsigned int mask_pcs_ready;
+	unsigned int mask_com_pcs_ready;
+
+	/* true, if PHY has a separate PHY_COM control block */
+	bool has_phy_com_ctrl;
+	/* true, if PHY has a reset for individual lanes */
+	bool has_lane_rst;
+	/* true, if PHY needs delay after POWER_DOWN */
+	bool has_pwrdn_delay;
+	/* power_down delay in usec */
+	int pwrdn_delay_min;
+	int pwrdn_delay_max;
+};
+
+/**
+ * struct qmp_phy - per-lane phy descriptor
+ *
+ * @phy: generic phy
+ * @tx: iomapped memory space for lane's tx
+ * @rx: iomapped memory space for lane's rx
+ * @pcs: iomapped memory space for lane's pcs
+ * @pipe_clk: pipe lock
+ * @index: lane index
+ * @qmp: QMP phy to which this lane belongs
+ * @lane_rst: lane's reset controller
+ */
+struct qmp_phy {
+	struct phy *phy;
+	void __iomem *tx;
+	void __iomem *rx;
+	void __iomem *pcs;
+	struct clk *pipe_clk;
+	unsigned int index;
+	struct qcom_qmp *qmp;
+	struct reset_control *lane_rst;
+};
+
+/**
+ * struct qcom_qmp - structure holding QMP phy block attributes
+ *
+ * @dev: device
+ * @serdes: iomapped memory space for phy's serdes
+ *
+ * @clks: array of clocks required by phy
+ * @resets: array of resets required by phy
+ * @vregs: regulator supplies bulk data
+ *
+ * @cfg: phy specific configuration
+ * @phys: array of per-lane phy descriptors
+ * @phy_mutex: mutex lock for PHY common block initialization
+ * @init_count: phy common block initialization count
+ */
+struct qcom_qmp {
+	struct device *dev;
+	void __iomem *serdes;
+
+	struct clk **clks;
+	struct reset_control **resets;
+	struct regulator_bulk_data *vregs;
+
+	const struct qmp_phy_cfg *cfg;
+	struct qmp_phy **phys;
+
+	struct mutex phy_mutex;
+	int init_count;
+};
+
+static inline void qphy_setbits(void __iomem *base, u32 offset, u32 val)
+{
+	u32 reg;
+
+	reg = readl(base + offset);
+	reg |= val;
+	writel(reg, base + offset);
+
+	/* ensure that above write is through */
+	readl(base + offset);
+}
+
+static inline void qphy_clrbits(void __iomem *base, u32 offset, u32 val)
+{
+	u32 reg;
+
+	reg = readl(base + offset);
+	reg &= ~val;
+	writel(reg, base + offset);
+
+	/* ensure that above write is through */
+	readl(base + offset);
+}
+
+/* list of clocks required by phy */
+static const char * const msm8996_phy_clk_l[] = {
+	"aux", "cfg_ahb", "ref",
+};
+
+/* list of resets */
+static const char * const msm8996_pciephy_reset_l[] = {
+	"phy", "common", "cfg",
+};
+
+static const char * const msm8996_usb3phy_reset_l[] = {
+	"phy", "common",
+};
+
+/* list of regulators */
+static const char * const msm8996_phy_vreg_l[] = {
+	"vdda-phy", "vdda-pll",
+};
+
+static const struct qmp_phy_cfg msm8996_pciephy_cfg = {
+	.type			= PHY_TYPE_PCIE,
+	.nlanes			= 3,
+
+	.serdes_tbl		= msm8996_pcie_serdes_tbl,
+	.serdes_tbl_num		= ARRAY_SIZE(msm8996_pcie_serdes_tbl),
+	.tx_tbl			= msm8996_pcie_tx_tbl,
+	.tx_tbl_num		= ARRAY_SIZE(msm8996_pcie_tx_tbl),
+	.rx_tbl			= msm8996_pcie_rx_tbl,
+	.rx_tbl_num		= ARRAY_SIZE(msm8996_pcie_rx_tbl),
+	.pcs_tbl		= msm8996_pcie_pcs_tbl,
+	.pcs_tbl_num		= ARRAY_SIZE(msm8996_pcie_pcs_tbl),
+	.clk_list		= msm8996_phy_clk_l,
+	.num_clks		= ARRAY_SIZE(msm8996_phy_clk_l),
+	.reset_list		= msm8996_pciephy_reset_l,
+	.num_resets		= ARRAY_SIZE(msm8996_pciephy_reset_l),
+	.vreg_list		= msm8996_phy_vreg_l,
+	.num_vregs		= ARRAY_SIZE(msm8996_phy_vreg_l),
+	.regs			= pciephy_regs_layout,
+
+	.start_ctrl		= PCS_START | PLL_READY_GATE_EN,
+	.pwrdn_ctrl		= SW_PWRDN | REFCLK_DRV_DSBL,
+	.mask_com_pcs_ready	= PCS_READY,
+
+	.has_phy_com_ctrl	= true,
+	.has_lane_rst		= true,
+	.has_pwrdn_delay	= true,
+	.pwrdn_delay_min	= POWER_DOWN_DELAY_US_MIN,
+	.pwrdn_delay_max	= POWER_DOWN_DELAY_US_MAX,
+};
+
+static const struct qmp_phy_cfg msm8996_usb3phy_cfg = {
+	.type			= PHY_TYPE_USB3,
+	.nlanes			= 1,
+
+	.serdes_tbl		= msm8996_usb3_serdes_tbl,
+	.serdes_tbl_num		= ARRAY_SIZE(msm8996_usb3_serdes_tbl),
+	.tx_tbl			= msm8996_usb3_tx_tbl,
+	.tx_tbl_num		= ARRAY_SIZE(msm8996_usb3_tx_tbl),
+	.rx_tbl			= msm8996_usb3_rx_tbl,
+	.rx_tbl_num		= ARRAY_SIZE(msm8996_usb3_rx_tbl),
+	.pcs_tbl		= msm8996_usb3_pcs_tbl,
+	.pcs_tbl_num		= ARRAY_SIZE(msm8996_usb3_pcs_tbl),
+	.clk_list		= msm8996_phy_clk_l,
+	.num_clks		= ARRAY_SIZE(msm8996_phy_clk_l),
+	.reset_list		= msm8996_usb3phy_reset_l,
+	.num_resets		= ARRAY_SIZE(msm8996_usb3phy_reset_l),
+	.vreg_list		= msm8996_phy_vreg_l,
+	.num_vregs		= ARRAY_SIZE(msm8996_phy_vreg_l),
+	.regs			= usb3phy_regs_layout,
+
+	.start_ctrl		= SERDES_START | PCS_START,
+	.pwrdn_ctrl		= SW_PWRDN,
+	.mask_pcs_ready		= PHYSTATUS,
+};
+
+static void qcom_qmp_phy_configure(void __iomem *base,
+				   const unsigned int *regs,
+				   const struct qmp_phy_init_tbl tbl[],
+				   int num)
+{
+	int i;
+	const struct qmp_phy_init_tbl *t = tbl;
+
+	if (!t)
+		return;
+
+	for (i = 0; i < num; i++, t++) {
+		if (t->in_layout)
+			writel(t->val, base + regs[t->offset]);
+		else
+			writel(t->val, base + t->offset);
+	}
+}
+
+static int qcom_qmp_phy_poweron(struct phy *phy)
+{
+	struct qmp_phy *qphy = phy_get_drvdata(phy);
+	struct qcom_qmp *qmp = qphy->qmp;
+	int num = qmp->cfg->num_vregs;
+	int ret;
+
+	dev_vdbg(&phy->dev, "Powering on QMP phy\n");
+
+	/* turn on regulator supplies */
+	ret = regulator_bulk_enable(num, qmp->vregs);
+	if (ret) {
+		dev_err(qmp->dev, "failed to enable regulators, err=%d\n", ret);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(qphy->pipe_clk);
+	if (ret) {
+		dev_err(qmp->dev, "pipe_clk enable failed, err=%d\n", ret);
+		regulator_bulk_disable(num, qmp->vregs);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int qcom_qmp_phy_poweroff(struct phy *phy)
+{
+	struct qmp_phy *qphy = phy_get_drvdata(phy);
+	struct qcom_qmp *qmp = qphy->qmp;
+
+	clk_disable_unprepare(qphy->pipe_clk);
+
+	regulator_bulk_disable(qmp->cfg->num_vregs, qmp->vregs);
+
+	return 0;
+}
+
+static int qcom_qmp_phy_com_init(struct qcom_qmp *qmp)
+{
+	const struct qmp_phy_cfg *cfg = qmp->cfg;
+	void __iomem *serdes = qmp->serdes;
+	int ret, i;
+
+	mutex_lock(&qmp->phy_mutex);
+	if (qmp->init_count++) {
+		mutex_unlock(&qmp->phy_mutex);
+		return 0;
+	}
+
+	for (i = 0; i < cfg->num_resets; i++) {
+		ret = reset_control_deassert(qmp->resets[i]);
+		if (ret) {
+			dev_err(qmp->dev, "%s reset deassert failed\n",
+				qmp->cfg->reset_list[i]);
+			while (--i >= 0)
+				reset_control_assert(qmp->resets[i]);
+			goto err_rst;
+		}
+	}
+
+	if (cfg->has_phy_com_ctrl)
+		qphy_setbits(serdes, cfg->regs[QPHY_COM_POWER_DOWN_CONTROL],
+			     SW_PWRDN);
+
+	/* Serdes configuration */
+	qcom_qmp_phy_configure(serdes, cfg->regs, cfg->serdes_tbl,
+			       cfg->serdes_tbl_num);
+
+	if (cfg->has_phy_com_ctrl) {
+		void __iomem *status;
+		unsigned int mask, val;
+
+		qphy_clrbits(serdes, cfg->regs[QPHY_COM_SW_RESET], SW_RESET);
+		qphy_setbits(serdes, cfg->regs[QPHY_COM_START_CONTROL],
+			     SERDES_START | PCS_START);
+
+		status = serdes + cfg->regs[QPHY_COM_PCS_READY_STATUS];
+		mask = cfg->mask_com_pcs_ready;
+
+		ret = readl_poll_timeout(status, val, (val & mask), 10,
+					 PHY_INIT_COMPLETE_TIMEOUT);
+		if (ret) {
+			dev_err(qmp->dev,
+				"phy common block init timed-out\n");
+			goto err_com_init;
+		}
+	}
+
+	mutex_unlock(&qmp->phy_mutex);
+
+	return 0;
+
+err_com_init:
+	while (--i >= 0)
+		reset_control_assert(qmp->resets[i]);
+err_rst:
+	mutex_unlock(&qmp->phy_mutex);
+	return ret;
+}
+
+static int qcom_qmp_phy_com_exit(struct qcom_qmp *qmp)
+{
+	const struct qmp_phy_cfg *cfg = qmp->cfg;
+	void __iomem *serdes = qmp->serdes;
+	int i = cfg->num_resets;
+
+	mutex_lock(&qmp->phy_mutex);
+	if (--qmp->init_count) {
+		mutex_unlock(&qmp->phy_mutex);
+		return 0;
+	}
+
+	if (cfg->has_phy_com_ctrl) {
+		qphy_setbits(serdes, cfg->regs[QPHY_COM_START_CONTROL],
+			     SERDES_START | PCS_START);
+		qphy_clrbits(serdes, cfg->regs[QPHY_COM_SW_RESET],
+			     SW_RESET);
+		qphy_setbits(serdes, cfg->regs[QPHY_COM_POWER_DOWN_CONTROL],
+			     SW_PWRDN);
+	}
+
+	while (--i >= 0)
+		reset_control_assert(qmp->resets[i]);
+
+	mutex_unlock(&qmp->phy_mutex);
+
+	return 0;
+}
+
+/* PHY Initialization */
+static int qcom_qmp_phy_init(struct phy *phy)
+{
+	struct qmp_phy *qphy = phy_get_drvdata(phy);
+	struct qcom_qmp *qmp = qphy->qmp;
+	const struct qmp_phy_cfg *cfg = qmp->cfg;
+	void __iomem *tx = qphy->tx;
+	void __iomem *rx = qphy->rx;
+	void __iomem *pcs = qphy->pcs;
+	void __iomem *status;
+	unsigned int mask, val;
+	int ret, i;
+
+	dev_vdbg(qmp->dev, "Initializing QMP phy\n");
+
+	for (i = 0; i < qmp->cfg->num_clks; i++) {
+		ret = clk_prepare_enable(qmp->clks[i]);
+		if (ret) {
+			dev_err(qmp->dev, "failed to enable %s clk, err=%d\n",
+				qmp->cfg->clk_list[i], ret);
+			while (--i >= 0)
+				clk_disable_unprepare(qmp->clks[i]);
+		}
+	}
+
+	ret = qcom_qmp_phy_com_init(qmp);
+	if (ret)
+		goto err_com_init;
+
+	if (cfg->has_lane_rst) {
+		ret = reset_control_deassert(qphy->lane_rst);
+		if (ret) {
+			dev_err(qmp->dev, "lane%d reset deassert failed\n",
+				qphy->index);
+			goto err_lane_rst;
+		}
+	}
+
+	/* Tx, Rx, and PCS configurations */
+	qcom_qmp_phy_configure(tx, cfg->regs, cfg->tx_tbl, cfg->tx_tbl_num);
+	qcom_qmp_phy_configure(rx, cfg->regs, cfg->rx_tbl, cfg->rx_tbl_num);
+	qcom_qmp_phy_configure(pcs, cfg->regs, cfg->pcs_tbl, cfg->pcs_tbl_num);
+
+	/*
+	 * Pull out PHY from POWER DOWN state.
+	 * This is active low enable signal to power-down PHY.
+	 */
+	qphy_setbits(pcs, QPHY_POWER_DOWN_CONTROL, cfg->pwrdn_ctrl);
+
+	if (cfg->has_pwrdn_delay)
+		usleep_range(cfg->pwrdn_delay_min, cfg->pwrdn_delay_max);
+
+	/* start SerDes and Phy-Coding-Sublayer */
+	qphy_setbits(pcs, cfg->regs[QPHY_START_CTRL], cfg->start_ctrl);
+
+	/* Pull PHY out of reset state */
+	qphy_clrbits(pcs, cfg->regs[QPHY_SW_RESET], SW_RESET);
+
+	status = pcs + cfg->regs[QPHY_PCS_READY_STATUS];
+	mask = cfg->mask_pcs_ready;
+
+	ret = readl_poll_timeout(status, val, !(val & mask), 1,
+				 PHY_INIT_COMPLETE_TIMEOUT);
+	if (ret) {
+		dev_err(qmp->dev, "phy initialization timed-out\n");
+		goto err_pcs_ready;
+	}
+
+	return ret;
+
+err_pcs_ready:
+	if (cfg->has_lane_rst)
+		reset_control_assert(qphy->lane_rst);
+err_lane_rst:
+	qcom_qmp_phy_com_exit(qmp);
+err_com_init:
+	while (--i >= 0)
+		clk_disable_unprepare(qmp->clks[i]);
+
+	return ret;
+}
+
+static int qcom_qmp_phy_exit(struct phy *phy)
+{
+	struct qmp_phy *qphy = phy_get_drvdata(phy);
+	struct qcom_qmp *qmp = qphy->qmp;
+	const struct qmp_phy_cfg *cfg = qmp->cfg;
+	int i = cfg->num_clks;
+
+	/* PHY reset */
+	qphy_setbits(qphy->pcs, cfg->regs[QPHY_SW_RESET], SW_RESET);
+
+	/* stop SerDes and Phy-Coding-Sublayer */
+	qphy_clrbits(qphy->pcs, cfg->regs[QPHY_START_CTRL], cfg->start_ctrl);
+
+	/* Put PHY into POWER DOWN state: active low */
+	qphy_clrbits(qphy->pcs, QPHY_POWER_DOWN_CONTROL, cfg->pwrdn_ctrl);
+
+	if (cfg->has_lane_rst)
+		reset_control_assert(qphy->lane_rst);
+
+	qcom_qmp_phy_com_exit(qmp);
+
+	while (--i >= 0)
+		clk_disable_unprepare(qmp->clks[i]);
+
+	return 0;
+}
+
+static int qcom_qmp_phy_vreg_init(struct device *dev)
+{
+	struct qcom_qmp *qmp = dev_get_drvdata(dev);
+	int num = qmp->cfg->num_vregs;
+	int i;
+
+	qmp->vregs = devm_kcalloc(dev, num, sizeof(qmp->vregs), GFP_KERNEL);
+	if (!qmp->vregs)
+		return -ENOMEM;
+
+	for (i = 0; i < num; i++)
+		qmp->vregs[i].supply = qmp->cfg->vreg_list[i];
+
+	return devm_regulator_bulk_get(dev, num, qmp->vregs);
+}
+
+static int qcom_qmp_phy_reset_init(struct device *dev)
+{
+	struct qcom_qmp *qmp = dev_get_drvdata(dev);
+	int i;
+
+	qmp->resets = devm_kcalloc(dev, qmp->cfg->num_resets,
+				   sizeof(*qmp->resets), GFP_KERNEL);
+	if (!qmp->resets)
+		return -ENOMEM;
+
+	for (i = 0; i < qmp->cfg->num_resets; i++) {
+		struct reset_control *rst;
+		const char *name = qmp->cfg->reset_list[i];
+
+		rst = devm_reset_control_get(dev, name);
+		if (IS_ERR(rst)) {
+			dev_err(dev, "failed to get %s reset\n", name);
+			return PTR_ERR(rst);
+		}
+		qmp->resets[i] = rst;
+	}
+
+	return 0;
+}
+
+static int qcom_qmp_phy_clk_init(struct device *dev)
+{
+	struct qcom_qmp *qmp = dev_get_drvdata(dev);
+	int ret, i;
+
+	qmp->clks = devm_kcalloc(dev, qmp->cfg->num_clks,
+				 sizeof(*qmp->clks), GFP_KERNEL);
+	if (!qmp->clks)
+		return -ENOMEM;
+
+	for (i = 0; i < qmp->cfg->num_clks; i++) {
+		struct clk *_clk;
+		const char *name = qmp->cfg->clk_list[i];
+
+		_clk = devm_clk_get(dev, name);
+		if (IS_ERR(_clk)) {
+			ret = PTR_ERR(_clk);
+			if (ret != -EPROBE_DEFER)
+				dev_err(dev, "failed to get %s clk, %d\n",
+					name, ret);
+			return ret;
+		}
+		qmp->clks[i] = _clk;
+	}
+
+	return 0;
+}
+
+/*
+ * Register a fixed rate pipe clock.
+ *
+ * The <s>_pipe_clksrc generated by PHY goes to the GCC that gate
+ * controls it. The <s>_pipe_clk coming out of the GCC is requested
+ * by the PHY driver for its operations.
+ * We register the <s>_pipe_clksrc here. The gcc driver takes care
+ * of assigning this <s>_pipe_clksrc as parent to <s>_pipe_clk.
+ * Below picture shows this relationship.
+ *
+ *         +---------------+
+ *         |   PHY block   |<<---------------------------------------+
+ *         |               |                                         |
+ *         |   +-------+   |                   +-----+               |
+ *   I/P---^-->|  PLL  |---^--->pipe_clksrc--->| GCC |--->pipe_clk---+
+ *    clk  |   +-------+   |                   +-----+
+ *         +---------------+
+ */
+static int phy_pipe_clk_register(struct qcom_qmp *qmp, int id)
+{
+	char name[24];
+	struct clk_fixed_rate *fixed;
+	struct clk_init_data init = { };
+
+	switch (qmp->cfg->type) {
+	case PHY_TYPE_USB3:
+		snprintf(name, sizeof(name), "usb3_phy_pipe_clk_src");
+		break;
+	case PHY_TYPE_PCIE:
+		snprintf(name, sizeof(name), "pcie_%d_pipe_clk_src", id);
+		break;
+	default:
+		/* not all phys register pipe clocks, so return success */
+		return 0;
+	}
+
+	fixed = devm_kzalloc(qmp->dev, sizeof(*fixed), GFP_KERNEL);
+	if (!fixed)
+		return -ENOMEM;
+
+	init.name = name;
+	init.ops = &clk_fixed_rate_ops;
+
+	/* controllers using QMP phys use 125MHz pipe clock interface */
+	fixed->fixed_rate = 125000000;
+	fixed->hw.init = &init;
+
+	return devm_clk_hw_register(qmp->dev, &fixed->hw);
+}
+
+static const struct phy_ops qcom_qmp_phy_gen_ops = {
+	.init		= qcom_qmp_phy_init,
+	.exit		= qcom_qmp_phy_exit,
+	.power_on	= qcom_qmp_phy_poweron,
+	.power_off	= qcom_qmp_phy_poweroff,
+	.owner		= THIS_MODULE,
+};
+
+static
+int qcom_qmp_phy_create(struct device *dev, struct device_node *np, int id)
+{
+	struct qcom_qmp *qmp = dev_get_drvdata(dev);
+	struct phy *generic_phy;
+	struct qmp_phy *qphy;
+	char prop_name[MAX_PROP_NAME];
+	int ret;
+
+	qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL);
+	if (!qphy)
+		return -ENOMEM;
+
+	/*
+	 * Get memory resources for each phy lane:
+	 * Resources are indexed as: tx -> 0; rx -> 1; pcs -> 2.
+	 */
+	qphy->tx = of_iomap(np, 0);
+	if (IS_ERR(qphy->tx))
+		return PTR_ERR(qphy->tx);
+
+	qphy->rx = of_iomap(np, 1);
+	if (IS_ERR(qphy->rx))
+		return PTR_ERR(qphy->rx);
+
+	qphy->pcs = of_iomap(np, 2);
+	if (IS_ERR(qphy->pcs))
+		return PTR_ERR(qphy->pcs);
+
+	/*
+	 * Get PHY's Pipe clock, if any. USB3 and PCIe are PIPE3
+	 * based phys, so they essentially have pipe clock. So,
+	 * we return error in case phy is USB3 or PIPE type.
+	 * Otherwise, we initialize pipe clock to NULL for
+	 * all phys that don't need this.
+	 */
+	snprintf(prop_name, sizeof(prop_name), "pipe%d", id);
+	qphy->pipe_clk = of_clk_get_by_name(np, prop_name);
+	if (IS_ERR(qphy->pipe_clk)) {
+		if (qmp->cfg->type == PHY_TYPE_PCIE ||
+		    qmp->cfg->type == PHY_TYPE_USB3) {
+			ret = PTR_ERR(qphy->pipe_clk);
+			if (ret != -EPROBE_DEFER)
+				dev_err(dev,
+					"failed to get lane%d pipe_clk, %d\n",
+					id, ret);
+			return ret;
+		}
+		qphy->pipe_clk = NULL;
+	}
+
+	/* Get lane reset, if any */
+	if (qmp->cfg->has_lane_rst) {
+		snprintf(prop_name, sizeof(prop_name), "lane%d", id);
+		qphy->lane_rst = of_reset_control_get(np, prop_name);
+		if (IS_ERR(qphy->lane_rst)) {
+			dev_err(dev, "failed to get lane%d reset\n", id);
+			return PTR_ERR(qphy->lane_rst);
+		}
+	}
+
+	generic_phy = devm_phy_create(dev, np, &qcom_qmp_phy_gen_ops);
+	if (IS_ERR(generic_phy)) {
+		ret = PTR_ERR(generic_phy);
+		dev_err(dev, "failed to create qphy %d\n", ret);
+		return ret;
+	}
+
+	qphy->phy = generic_phy;
+	qphy->index = id;
+	qphy->qmp = qmp;
+	qmp->phys[id] = qphy;
+	phy_set_drvdata(generic_phy, qphy);
+
+	return 0;
+}
+
+static const struct of_device_id qcom_qmp_phy_of_match_table[] = {
+	{
+		.compatible = "qcom,msm8996-qmp-pcie-phy",
+		.data = &msm8996_pciephy_cfg,
+	}, {
+		.compatible = "qcom,msm8996-qmp-usb3-phy",
+		.data = &msm8996_usb3phy_cfg,
+	},
+	{ },
+};
+MODULE_DEVICE_TABLE(of, qcom_qmp_phy_of_match_table);
+
+static int qcom_qmp_phy_probe(struct platform_device *pdev)
+{
+	struct qcom_qmp *qmp;
+	struct device *dev = &pdev->dev;
+	struct resource *res;
+	struct device_node *child;
+	struct phy_provider *phy_provider;
+	void __iomem *base;
+	int num, id;
+	int ret;
+
+	qmp = devm_kzalloc(dev, sizeof(*qmp), GFP_KERNEL);
+	if (!qmp)
+		return -ENOMEM;
+
+	qmp->dev = dev;
+	dev_set_drvdata(dev, qmp);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	/* per PHY serdes; usually located at base address */
+	qmp->serdes = base;
+
+	mutex_init(&qmp->phy_mutex);
+
+	/* Get the specific init parameters of QMP phy */
+	qmp->cfg = of_device_get_match_data(dev);
+
+	ret = qcom_qmp_phy_clk_init(dev);
+	if (ret)
+		return ret;
+
+	ret = qcom_qmp_phy_reset_init(dev);
+	if (ret)
+		return ret;
+
+	ret = qcom_qmp_phy_vreg_init(dev);
+	if (ret) {
+		dev_err(dev, "failed to get regulator supplies\n");
+		return ret;
+	}
+
+	num = of_get_available_child_count(dev->of_node);
+	/* do we have a rogue child node ? */
+	if (num > qmp->cfg->nlanes)
+		return -EINVAL;
+
+	qmp->phys = devm_kcalloc(dev, num, sizeof(*qmp->phys), GFP_KERNEL);
+	if (!qmp->phys)
+		return -ENOMEM;
+
+	id = 0;
+	for_each_available_child_of_node(dev->of_node, child) {
+		/* Create per-lane phy */
+		ret = qcom_qmp_phy_create(dev, child, id);
+		if (ret) {
+			dev_err(dev, "failed to create lane%d phy, %d\n",
+				id, ret);
+			return ret;
+		}
+
+		/*
+		 * Register the pipe clock provided by phy.
+		 * See function description to see details of this pipe clock.
+		 */
+		ret = phy_pipe_clk_register(qmp, id);
+		if (ret) {
+			dev_err(qmp->dev,
+				"failed to register pipe clock source\n");
+			return ret;
+		}
+		id++;
+	}
+
+	phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+	if (!IS_ERR(phy_provider))
+		dev_info(dev, "Registered Qcom-QMP phy\n");
+
+	return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static struct platform_driver qcom_qmp_phy_driver = {
+	.probe		= qcom_qmp_phy_probe,
+	.driver = {
+		.name	= "qcom-qmp-phy",
+		.of_match_table = qcom_qmp_phy_of_match_table,
+	},
+};
+
+module_platform_driver(qcom_qmp_phy_driver);
+
+MODULE_AUTHOR("Vivek Gautam <vivek.gautam@codeaurora.org>");
+MODULE_DESCRIPTION("Qualcomm QMP PHY driver");
+MODULE_LICENSE("GPL v2");

+ 493 - 0
drivers/phy/phy-qcom-qusb2.c

@@ -0,0 +1,493 @@
+/*
+ * Copyright (c) 2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+
+#define QUSB2PHY_PLL_TEST		0x04
+#define CLK_REF_SEL			BIT(7)
+
+#define QUSB2PHY_PLL_TUNE		0x08
+#define QUSB2PHY_PLL_USER_CTL1		0x0c
+#define QUSB2PHY_PLL_USER_CTL2		0x10
+#define QUSB2PHY_PLL_AUTOPGM_CTL1	0x1c
+#define QUSB2PHY_PLL_PWR_CTRL		0x18
+
+#define QUSB2PHY_PLL_STATUS		0x38
+#define PLL_LOCKED			BIT(5)
+
+#define QUSB2PHY_PORT_TUNE1		0x80
+#define QUSB2PHY_PORT_TUNE2		0x84
+#define QUSB2PHY_PORT_TUNE3		0x88
+#define QUSB2PHY_PORT_TUNE4		0x8c
+#define QUSB2PHY_PORT_TUNE5		0x90
+#define QUSB2PHY_PORT_TEST2		0x9c
+
+#define QUSB2PHY_PORT_POWERDOWN		0xb4
+#define CLAMP_N_EN			BIT(5)
+#define FREEZIO_N			BIT(1)
+#define POWER_DOWN			BIT(0)
+
+#define QUSB2PHY_REFCLK_ENABLE		BIT(0)
+
+#define PHY_CLK_SCHEME_SEL		BIT(0)
+
+struct qusb2_phy_init_tbl {
+	unsigned int offset;
+	unsigned int val;
+};
+
+#define QUSB2_PHY_INIT_CFG(o, v) \
+	{			\
+		.offset = o,	\
+		.val = v,	\
+	}
+
+static const struct qusb2_phy_init_tbl msm8996_init_tbl[] = {
+	QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE1, 0xf8),
+	QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE2, 0xb3),
+	QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE3, 0x83),
+	QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE4, 0xc0),
+	QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_TUNE, 0x30),
+	QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_USER_CTL1, 0x79),
+	QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_USER_CTL2, 0x21),
+	QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TEST2, 0x14),
+	QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_AUTOPGM_CTL1, 0x9f),
+	QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_PWR_CTRL, 0x00),
+};
+
+struct qusb2_phy_cfg {
+	const struct qusb2_phy_init_tbl *tbl;
+	/* number of entries in the table */
+	unsigned int tbl_num;
+	/* offset to PHY_CLK_SCHEME register in TCSR map */
+	unsigned int clk_scheme_offset;
+};
+
+static const struct qusb2_phy_cfg msm8996_phy_cfg = {
+	.tbl = msm8996_init_tbl,
+	.tbl_num = ARRAY_SIZE(msm8996_init_tbl),
+};
+
+static const char * const qusb2_phy_vreg_names[] = {
+	"vdda-pll", "vdda-phy-dpdm",
+};
+
+#define QUSB2_NUM_VREGS		ARRAY_SIZE(qusb2_phy_vreg_names)
+
+/**
+ * struct qusb2_phy - structure holding qusb2 phy attributes
+ *
+ * @phy: generic phy
+ * @base: iomapped memory space for qubs2 phy
+ *
+ * @cfg_ahb_clk: AHB2PHY interface clock
+ * @ref_clk: phy reference clock
+ * @iface_clk: phy interface clock
+ * @phy_reset: phy reset control
+ * @vregs: regulator supplies bulk data
+ *
+ * @tcsr: TCSR syscon register map
+ * @cell: nvmem cell containing phy tuning value
+ *
+ * @cfg: phy config data
+ * @has_se_clk_scheme: indicate if PHY has single-ended ref clock scheme
+ */
+struct qusb2_phy {
+	struct phy *phy;
+	void __iomem *base;
+
+	struct clk *cfg_ahb_clk;
+	struct clk *ref_clk;
+	struct clk *iface_clk;
+	struct reset_control *phy_reset;
+	struct regulator_bulk_data vregs[QUSB2_NUM_VREGS];
+
+	struct regmap *tcsr;
+	struct nvmem_cell *cell;
+
+	const struct qusb2_phy_cfg *cfg;
+	bool has_se_clk_scheme;
+};
+
+static inline void qusb2_setbits(void __iomem *base, u32 offset, u32 val)
+{
+	u32 reg;
+
+	reg = readl(base + offset);
+	reg |= val;
+	writel(reg, base + offset);
+
+	/* Ensure above write is completed */
+	readl(base + offset);
+}
+
+static inline void qusb2_clrbits(void __iomem *base, u32 offset, u32 val)
+{
+	u32 reg;
+
+	reg = readl(base + offset);
+	reg &= ~val;
+	writel(reg, base + offset);
+
+	/* Ensure above write is completed */
+	readl(base + offset);
+}
+
+static inline
+void qcom_qusb2_phy_configure(void __iomem *base,
+			      const struct qusb2_phy_init_tbl tbl[], int num)
+{
+	int i;
+
+	for (i = 0; i < num; i++)
+		writel(tbl[i].val, base + tbl[i].offset);
+}
+
+/*
+ * Fetches HS Tx tuning value from nvmem and sets the
+ * QUSB2PHY_PORT_TUNE2 register.
+ * For error case, skip setting the value and use the default value.
+ */
+static void qusb2_phy_set_tune2_param(struct qusb2_phy *qphy)
+{
+	struct device *dev = &qphy->phy->dev;
+	u8 *val;
+
+	/*
+	 * Read efuse register having TUNE2 parameter's high nibble.
+	 * If efuse register shows value as 0x0, or if we fail to find
+	 * a valid efuse register settings, then use default value
+	 * as 0xB for high nibble that we have already set while
+	 * configuring phy.
+	 */
+	val = nvmem_cell_read(qphy->cell, NULL);
+	if (IS_ERR(val) || !val[0]) {
+		dev_dbg(dev, "failed to read a valid hs-tx trim value\n");
+		return;
+	}
+
+	/* Fused TUNE2 value is the higher nibble only */
+	qusb2_setbits(qphy->base, QUSB2PHY_PORT_TUNE2, val[0] << 0x4);
+}
+
+static int qusb2_phy_poweron(struct phy *phy)
+{
+	struct qusb2_phy *qphy = phy_get_drvdata(phy);
+	int num = ARRAY_SIZE(qphy->vregs);
+	int ret;
+
+	dev_vdbg(&phy->dev, "%s(): Powering-on QUSB2 phy\n", __func__);
+
+	/* turn on regulator supplies */
+	ret = regulator_bulk_enable(num, qphy->vregs);
+	if (ret)
+		return ret;
+
+	ret = clk_prepare_enable(qphy->iface_clk);
+	if (ret) {
+		dev_err(&phy->dev, "failed to enable iface_clk, %d\n", ret);
+		regulator_bulk_disable(num, qphy->vregs);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int qusb2_phy_poweroff(struct phy *phy)
+{
+	struct qusb2_phy *qphy = phy_get_drvdata(phy);
+
+	clk_disable_unprepare(qphy->iface_clk);
+
+	regulator_bulk_disable(ARRAY_SIZE(qphy->vregs), qphy->vregs);
+
+	return 0;
+}
+
+static int qusb2_phy_init(struct phy *phy)
+{
+	struct qusb2_phy *qphy = phy_get_drvdata(phy);
+	unsigned int val;
+	unsigned int clk_scheme;
+	int ret;
+
+	dev_vdbg(&phy->dev, "%s(): Initializing QUSB2 phy\n", __func__);
+
+	/* enable ahb interface clock to program phy */
+	ret = clk_prepare_enable(qphy->cfg_ahb_clk);
+	if (ret) {
+		dev_err(&phy->dev, "failed to enable cfg ahb clock, %d\n", ret);
+		return ret;
+	}
+
+	/* Perform phy reset */
+	ret = reset_control_assert(qphy->phy_reset);
+	if (ret) {
+		dev_err(&phy->dev, "failed to assert phy_reset, %d\n", ret);
+		goto disable_ahb_clk;
+	}
+
+	/* 100 us delay to keep PHY in reset mode */
+	usleep_range(100, 150);
+
+	ret = reset_control_deassert(qphy->phy_reset);
+	if (ret) {
+		dev_err(&phy->dev, "failed to de-assert phy_reset, %d\n", ret);
+		goto disable_ahb_clk;
+	}
+
+	/* Disable the PHY */
+	qusb2_setbits(qphy->base, QUSB2PHY_PORT_POWERDOWN,
+		      CLAMP_N_EN | FREEZIO_N | POWER_DOWN);
+
+	/* save reset value to override reference clock scheme later */
+	val = readl(qphy->base + QUSB2PHY_PLL_TEST);
+
+	qcom_qusb2_phy_configure(qphy->base, qphy->cfg->tbl,
+				 qphy->cfg->tbl_num);
+
+	/* Set efuse value for tuning the PHY */
+	qusb2_phy_set_tune2_param(qphy);
+
+	/* Enable the PHY */
+	qusb2_clrbits(qphy->base, QUSB2PHY_PORT_POWERDOWN, POWER_DOWN);
+
+	/* Required to get phy pll lock successfully */
+	usleep_range(150, 160);
+
+	/* Default is single-ended clock on msm8996 */
+	qphy->has_se_clk_scheme = true;
+	/*
+	 * read TCSR_PHY_CLK_SCHEME register to check if single-ended
+	 * clock scheme is selected. If yes, then disable differential
+	 * ref_clk and use single-ended clock, otherwise use differential
+	 * ref_clk only.
+	 */
+	if (qphy->tcsr) {
+		ret = regmap_read(qphy->tcsr, qphy->cfg->clk_scheme_offset,
+				  &clk_scheme);
+		if (ret) {
+			dev_err(&phy->dev, "failed to read clk scheme reg\n");
+			goto assert_phy_reset;
+		}
+
+		/* is it a differential clock scheme ? */
+		if (!(clk_scheme & PHY_CLK_SCHEME_SEL)) {
+			dev_vdbg(&phy->dev, "%s(): select differential clk\n",
+				 __func__);
+			qphy->has_se_clk_scheme = false;
+		} else {
+			dev_vdbg(&phy->dev, "%s(): select single-ended clk\n",
+				 __func__);
+		}
+	}
+
+	if (!qphy->has_se_clk_scheme) {
+		val &= ~CLK_REF_SEL;
+		ret = clk_prepare_enable(qphy->ref_clk);
+		if (ret) {
+			dev_err(&phy->dev, "failed to enable ref clk, %d\n",
+				ret);
+			goto assert_phy_reset;
+		}
+	} else {
+		val |= CLK_REF_SEL;
+	}
+
+	writel(val, qphy->base + QUSB2PHY_PLL_TEST);
+
+	/* ensure above write is through */
+	readl(qphy->base + QUSB2PHY_PLL_TEST);
+
+	/* Required to get phy pll lock successfully */
+	usleep_range(100, 110);
+
+	val = readb(qphy->base + QUSB2PHY_PLL_STATUS);
+	if (!(val & PLL_LOCKED)) {
+		dev_err(&phy->dev,
+			"QUSB2PHY pll lock failed: status reg = %x\n", val);
+		ret = -EBUSY;
+		goto disable_ref_clk;
+	}
+
+	return 0;
+
+disable_ref_clk:
+	if (!qphy->has_se_clk_scheme)
+		clk_disable_unprepare(qphy->ref_clk);
+assert_phy_reset:
+	reset_control_assert(qphy->phy_reset);
+disable_ahb_clk:
+	clk_disable_unprepare(qphy->cfg_ahb_clk);
+	return ret;
+}
+
+static int qusb2_phy_exit(struct phy *phy)
+{
+	struct qusb2_phy *qphy = phy_get_drvdata(phy);
+
+	/* Disable the PHY */
+	qusb2_setbits(qphy->base, QUSB2PHY_PORT_POWERDOWN,
+		      CLAMP_N_EN | FREEZIO_N | POWER_DOWN);
+
+	if (!qphy->has_se_clk_scheme)
+		clk_disable_unprepare(qphy->ref_clk);
+
+	reset_control_assert(qphy->phy_reset);
+
+	clk_disable_unprepare(qphy->cfg_ahb_clk);
+
+	return 0;
+}
+
+static const struct phy_ops qusb2_phy_gen_ops = {
+	.init		= qusb2_phy_init,
+	.exit		= qusb2_phy_exit,
+	.power_on	= qusb2_phy_poweron,
+	.power_off	= qusb2_phy_poweroff,
+	.owner		= THIS_MODULE,
+};
+
+static const struct of_device_id qusb2_phy_of_match_table[] = {
+	{
+		.compatible	= "qcom,msm8996-qusb2-phy",
+		.data		= &msm8996_phy_cfg,
+	},
+	{ },
+};
+MODULE_DEVICE_TABLE(of, qusb2_phy_of_match_table);
+
+static int qusb2_phy_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct qusb2_phy *qphy;
+	struct phy_provider *phy_provider;
+	struct phy *generic_phy;
+	struct resource *res;
+	int ret, i;
+	int num;
+
+	qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL);
+	if (!qphy)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	qphy->base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(qphy->base))
+		return PTR_ERR(qphy->base);
+
+	qphy->cfg_ahb_clk = devm_clk_get(dev, "cfg_ahb");
+	if (IS_ERR(qphy->cfg_ahb_clk)) {
+		ret = PTR_ERR(qphy->cfg_ahb_clk);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "failed to get cfg ahb clk, %d\n", ret);
+		return ret;
+	}
+
+	qphy->ref_clk = devm_clk_get(dev, "ref");
+	if (IS_ERR(qphy->ref_clk)) {
+		ret = PTR_ERR(qphy->ref_clk);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "failed to get ref clk, %d\n", ret);
+		return ret;
+	}
+
+	qphy->iface_clk = devm_clk_get(dev, "iface");
+	if (IS_ERR(qphy->iface_clk)) {
+		ret = PTR_ERR(qphy->iface_clk);
+		if (ret == -EPROBE_DEFER)
+			return ret;
+		qphy->iface_clk = NULL;
+		dev_dbg(dev, "failed to get iface clk, %d\n", ret);
+	}
+
+	qphy->phy_reset = devm_reset_control_get_by_index(&pdev->dev, 0);
+	if (IS_ERR(qphy->phy_reset)) {
+		dev_err(dev, "failed to get phy core reset\n");
+		return PTR_ERR(qphy->phy_reset);
+	}
+
+	num = ARRAY_SIZE(qphy->vregs);
+	for (i = 0; i < num; i++)
+		qphy->vregs[i].supply = qusb2_phy_vreg_names[i];
+
+	ret = devm_regulator_bulk_get(dev, num, qphy->vregs);
+	if (ret) {
+		dev_err(dev, "failed to get regulator supplies\n");
+		return ret;
+	}
+
+	/* Get the specific init parameters of QMP phy */
+	qphy->cfg = of_device_get_match_data(dev);
+
+	qphy->tcsr = syscon_regmap_lookup_by_phandle(dev->of_node,
+							"qcom,tcsr-syscon");
+	if (IS_ERR(qphy->tcsr)) {
+		dev_dbg(dev, "failed to lookup TCSR regmap\n");
+		qphy->tcsr = NULL;
+	}
+
+	qphy->cell = devm_nvmem_cell_get(dev, NULL);
+	if (IS_ERR(qphy->cell)) {
+		if (PTR_ERR(qphy->cell) == -EPROBE_DEFER)
+			return -EPROBE_DEFER;
+		qphy->cell = NULL;
+		dev_dbg(dev, "failed to lookup tune2 hstx trim value\n");
+	}
+
+	generic_phy = devm_phy_create(dev, NULL, &qusb2_phy_gen_ops);
+	if (IS_ERR(generic_phy)) {
+		ret = PTR_ERR(generic_phy);
+		dev_err(dev, "failed to create phy, %d\n", ret);
+		return ret;
+	}
+	qphy->phy = generic_phy;
+
+	dev_set_drvdata(dev, qphy);
+	phy_set_drvdata(generic_phy, qphy);
+
+	phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+	if (!IS_ERR(phy_provider))
+		dev_info(dev, "Registered Qcom-QUSB2 phy\n");
+
+	return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static struct platform_driver qusb2_phy_driver = {
+	.probe		= qusb2_phy_probe,
+	.driver = {
+		.name	= "qcom-qusb2-phy",
+		.of_match_table = qusb2_phy_of_match_table,
+	},
+};
+
+module_platform_driver(qusb2_phy_driver);
+
+MODULE_AUTHOR("Vivek Gautam <vivek.gautam@codeaurora.org>");
+MODULE_DESCRIPTION("Qualcomm QUSB2 PHY driver");
+MODULE_LICENSE("GPL v2");

+ 24 - 7
drivers/phy/phy-rcar-gen3-usb2.c

@@ -20,6 +20,7 @@
 #include <linux/of_address.h>
 #include <linux/phy/phy.h>
 #include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
 #include <linux/regulator/consumer.h>
 #include <linux/workqueue.h>
 
@@ -395,7 +396,7 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev)
 	struct rcar_gen3_chan *channel;
 	struct phy_provider *provider;
 	struct resource *res;
-	int irq;
+	int irq, ret = 0;
 
 	if (!dev->of_node) {
 		dev_err(dev, "This driver needs device tree\n");
@@ -434,17 +435,24 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev)
 		}
 	}
 
-	/* devm_phy_create() will call pm_runtime_enable(dev); */
+	/*
+	 * devm_phy_create() will call pm_runtime_enable(&phy->dev);
+	 * And then, phy-core will manage runtime pm for this device.
+	 */
+	pm_runtime_enable(dev);
 	channel->phy = devm_phy_create(dev, NULL, &rcar_gen3_phy_usb2_ops);
 	if (IS_ERR(channel->phy)) {
 		dev_err(dev, "Failed to create USB2 PHY\n");
-		return PTR_ERR(channel->phy);
+		ret = PTR_ERR(channel->phy);
+		goto error;
 	}
 
 	channel->vbus = devm_regulator_get_optional(dev, "vbus");
 	if (IS_ERR(channel->vbus)) {
-		if (PTR_ERR(channel->vbus) == -EPROBE_DEFER)
-			return PTR_ERR(channel->vbus);
+		if (PTR_ERR(channel->vbus) == -EPROBE_DEFER) {
+			ret = PTR_ERR(channel->vbus);
+			goto error;
+		}
 		channel->vbus = NULL;
 	}
 
@@ -454,15 +462,22 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev)
 	provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
 	if (IS_ERR(provider)) {
 		dev_err(dev, "Failed to register PHY provider\n");
+		ret = PTR_ERR(provider);
+		goto error;
 	} else if (channel->has_otg) {
 		int ret;
 
 		ret = device_create_file(dev, &dev_attr_role);
 		if (ret < 0)
-			return ret;
+			goto error;
 	}
 
-	return PTR_ERR_OR_ZERO(provider);
+	return 0;
+
+error:
+	pm_runtime_disable(dev);
+
+	return ret;
 }
 
 static int rcar_gen3_phy_usb2_remove(struct platform_device *pdev)
@@ -472,6 +487,8 @@ static int rcar_gen3_phy_usb2_remove(struct platform_device *pdev)
 	if (channel->has_otg)
 		device_remove_file(&pdev->dev, &dev_attr_role);
 
+	pm_runtime_disable(&pdev->dev);
+
 	return 0;
 };
 

+ 47 - 6
drivers/phy/phy-rockchip-inno-usb2.c

@@ -554,8 +554,7 @@ static void rockchip_usb2phy_otg_sm_work(struct work_struct *work)
 			case USB_CHG_STATE_DETECTED:
 				switch (rphy->chg_type) {
 				case POWER_SUPPLY_TYPE_USB:
-					dev_dbg(&rport->phy->dev,
-						"sdp cable is connecetd\n");
+					dev_dbg(&rport->phy->dev, "sdp cable is connected\n");
 					rockchip_usb2phy_power_on(rport->phy);
 					rport->state = OTG_STATE_B_PERIPHERAL;
 					notify_charger = true;
@@ -563,16 +562,14 @@ static void rockchip_usb2phy_otg_sm_work(struct work_struct *work)
 					cable = EXTCON_CHG_USB_SDP;
 					break;
 				case POWER_SUPPLY_TYPE_USB_DCP:
-					dev_dbg(&rport->phy->dev,
-						"dcp cable is connecetd\n");
+					dev_dbg(&rport->phy->dev, "dcp cable is connected\n");
 					rockchip_usb2phy_power_off(rport->phy);
 					notify_charger = true;
 					sch_work = true;
 					cable = EXTCON_CHG_USB_DCP;
 					break;
 				case POWER_SUPPLY_TYPE_USB_CDP:
-					dev_dbg(&rport->phy->dev,
-						"cdp cable is connecetd\n");
+					dev_dbg(&rport->phy->dev, "cdp cable is connected\n");
 					rockchip_usb2phy_power_on(rport->phy);
 					rport->state = OTG_STATE_B_PERIPHERAL;
 					notify_charger = true;
@@ -1141,6 +1138,49 @@ disable_clks:
 	return ret;
 }
 
+static const struct rockchip_usb2phy_cfg rk3328_phy_cfgs[] = {
+	{
+		.reg = 0x100,
+		.num_ports	= 2,
+		.clkout_ctl	= { 0x108, 4, 4, 1, 0 },
+		.port_cfgs	= {
+			[USB2PHY_PORT_OTG] = {
+				.phy_sus	= { 0x0100, 15, 0, 0, 0x1d1 },
+				.bvalid_det_en	= { 0x0110, 2, 2, 0, 1 },
+				.bvalid_det_st	= { 0x0114, 2, 2, 0, 1 },
+				.bvalid_det_clr = { 0x0118, 2, 2, 0, 1 },
+				.ls_det_en	= { 0x0110, 0, 0, 0, 1 },
+				.ls_det_st	= { 0x0114, 0, 0, 0, 1 },
+				.ls_det_clr	= { 0x0118, 0, 0, 0, 1 },
+				.utmi_avalid	= { 0x0120, 10, 10, 0, 1 },
+				.utmi_bvalid	= { 0x0120, 9, 9, 0, 1 },
+				.utmi_ls	= { 0x0120, 5, 4, 0, 1 },
+			},
+			[USB2PHY_PORT_HOST] = {
+				.phy_sus	= { 0x104, 15, 0, 0, 0x1d1 },
+				.ls_det_en	= { 0x110, 1, 1, 0, 1 },
+				.ls_det_st	= { 0x114, 1, 1, 0, 1 },
+				.ls_det_clr	= { 0x118, 1, 1, 0, 1 },
+				.utmi_ls	= { 0x120, 17, 16, 0, 1 },
+				.utmi_hstdet	= { 0x120, 19, 19, 0, 1 }
+			}
+		},
+		.chg_det = {
+			.opmode		= { 0x0100, 3, 0, 5, 1 },
+			.cp_det		= { 0x0120, 24, 24, 0, 1 },
+			.dcp_det	= { 0x0120, 23, 23, 0, 1 },
+			.dp_det		= { 0x0120, 25, 25, 0, 1 },
+			.idm_sink_en	= { 0x0108, 8, 8, 0, 1 },
+			.idp_sink_en	= { 0x0108, 7, 7, 0, 1 },
+			.idp_src_en	= { 0x0108, 9, 9, 0, 1 },
+			.rdm_pdwn_en	= { 0x0108, 10, 10, 0, 1 },
+			.vdm_src_en	= { 0x0108, 12, 12, 0, 1 },
+			.vdp_src_en	= { 0x0108, 11, 11, 0, 1 },
+		},
+	},
+	{ /* sentinel */ }
+};
+
 static const struct rockchip_usb2phy_cfg rk3366_phy_cfgs[] = {
 	{
 		.reg = 0x700,
@@ -1223,6 +1263,7 @@ static const struct rockchip_usb2phy_cfg rk3399_phy_cfgs[] = {
 };
 
 static const struct of_device_id rockchip_usb2phy_dt_match[] = {
+	{ .compatible = "rockchip,rk3328-usb2phy", .data = &rk3328_phy_cfgs },
 	{ .compatible = "rockchip,rk3366-usb2phy", .data = &rk3366_phy_cfgs },
 	{ .compatible = "rockchip,rk3399-usb2phy", .data = &rk3399_phy_cfgs },
 	{}

+ 19 - 0
drivers/phy/phy-rockchip-usb.c

@@ -66,6 +66,7 @@ struct rockchip_usb_phy {
 	struct phy	*phy;
 	bool		uart_enabled;
 	struct reset_control *reset;
+	struct regulator *vbus;
 };
 
 static int rockchip_usb_phy_power(struct rockchip_usb_phy *phy,
@@ -88,6 +89,9 @@ static void rockchip_usb_phy480m_disable(struct clk_hw *hw)
 						    struct rockchip_usb_phy,
 						    clk480m_hw);
 
+	if (phy->vbus)
+		regulator_disable(phy->vbus);
+
 	/* Power down usb phy analog blocks by set siddq 1 */
 	rockchip_usb_phy_power(phy, 1);
 }
@@ -143,6 +147,14 @@ static int rockchip_usb_phy_power_on(struct phy *_phy)
 	if (phy->uart_enabled)
 		return -EBUSY;
 
+	if (phy->vbus) {
+		int ret;
+
+		ret = regulator_enable(phy->vbus);
+		if (ret)
+			return ret;
+	}
+
 	return clk_prepare_enable(phy->clk480m);
 }
 
@@ -268,6 +280,13 @@ static int rockchip_usb_phy_init(struct rockchip_usb_phy_base *base,
 	}
 	phy_set_drvdata(rk_phy->phy, rk_phy);
 
+	rk_phy->vbus = devm_regulator_get_optional(&rk_phy->phy->dev, "vbus");
+	if (IS_ERR(rk_phy->vbus)) {
+		if (PTR_ERR(rk_phy->vbus) == -EPROBE_DEFER)
+			return PTR_ERR(rk_phy->vbus);
+		rk_phy->vbus = NULL;
+	}
+
 	/*
 	 * When acting as uart-pipe, just keep clock on otherwise
 	 * only power up usb phy when it use, so disable it when init

+ 37 - 21
drivers/phy/phy-sun4i-usb.c

@@ -49,12 +49,14 @@
 #define REG_PHYBIST			0x08
 #define REG_PHYTUNE			0x0c
 #define REG_PHYCTL_A33			0x10
-#define REG_PHY_UNK_H3			0x20
+#define REG_PHY_OTGCTL			0x20
 
 #define REG_PMU_UNK1			0x10
 
 #define PHYCTL_DATA			BIT(7)
 
+#define OTGCTL_ROUTE_MUSB		BIT(0)
+
 #define SUNXI_AHB_ICHR8_EN		BIT(10)
 #define SUNXI_AHB_INCR4_BURST_EN	BIT(9)
 #define SUNXI_AHB_INCRX_ALIGN_EN	BIT(8)
@@ -110,6 +112,7 @@ struct sun4i_usb_phy_cfg {
 	u8 phyctl_offset;
 	bool dedicated_clocks;
 	bool enable_pmu_unk1;
+	bool phy0_dual_route;
 };
 
 struct sun4i_usb_phy_data {
@@ -188,10 +191,8 @@ static void sun4i_usb_phy_write(struct sun4i_usb_phy *phy, u32 addr, u32 data,
 
 	spin_lock_irqsave(&phy_data->reg_lock, flags);
 
-	if (phy_data->cfg->type == sun8i_a33_phy ||
-	    phy_data->cfg->type == sun50i_a64_phy ||
-	    phy_data->cfg->type == sun8i_v3s_phy) {
-		/* A33 or A64 needs us to set phyctl to 0 explicitly */
+	if (phy_data->cfg->phyctl_offset == REG_PHYCTL_A33) {
+		/* SoCs newer than A33 need us to set phyctl to 0 explicitly */
 		writel(0, phyctl);
 	}
 
@@ -271,23 +272,16 @@ static int sun4i_usb_phy_init(struct phy *_phy)
 		writel(val & ~2, phy->pmu + REG_PMU_UNK1);
 	}
 
-	if (data->cfg->type == sun8i_h3_phy) {
-		if (phy->index == 0) {
-			val = readl(data->base + REG_PHY_UNK_H3);
-			writel(val & ~1, data->base + REG_PHY_UNK_H3);
-		}
-	} else {
-		/* Enable USB 45 Ohm resistor calibration */
-		if (phy->index == 0)
-			sun4i_usb_phy_write(phy, PHY_RES45_CAL_EN, 0x01, 1);
+	/* Enable USB 45 Ohm resistor calibration */
+	if (phy->index == 0)
+		sun4i_usb_phy_write(phy, PHY_RES45_CAL_EN, 0x01, 1);
 
-		/* Adjust PHY's magnitude and rate */
-		sun4i_usb_phy_write(phy, PHY_TX_AMPLITUDE_TUNE, 0x14, 5);
+	/* Adjust PHY's magnitude and rate */
+	sun4i_usb_phy_write(phy, PHY_TX_AMPLITUDE_TUNE, 0x14, 5);
 
-		/* Disconnect threshold adjustment */
-		sun4i_usb_phy_write(phy, PHY_DISCON_TH_SEL,
-				    data->cfg->disc_thresh, 2);
-	}
+	/* Disconnect threshold adjustment */
+	sun4i_usb_phy_write(phy, PHY_DISCON_TH_SEL,
+			    data->cfg->disc_thresh, 2);
 
 	sun4i_usb_phy_passby(phy, 1);
 
@@ -486,6 +480,21 @@ static const struct phy_ops sun4i_usb_phy_ops = {
 	.owner		= THIS_MODULE,
 };
 
+static void sun4i_usb_phy0_reroute(struct sun4i_usb_phy_data *data, int id_det)
+{
+	u32 regval;
+
+	regval = readl(data->base + REG_PHY_OTGCTL);
+	if (id_det == 0) {
+		/* Host mode. Route phy0 to EHCI/OHCI */
+		regval &= ~OTGCTL_ROUTE_MUSB;
+	} else {
+		/* Peripheral mode. Route phy0 to MUSB */
+		regval |= OTGCTL_ROUTE_MUSB;
+	}
+	writel(regval, data->base + REG_PHY_OTGCTL);
+}
+
 static void sun4i_usb_phy0_id_vbus_det_scan(struct work_struct *work)
 {
 	struct sun4i_usb_phy_data *data =
@@ -546,6 +555,10 @@ static void sun4i_usb_phy0_id_vbus_det_scan(struct work_struct *work)
 			sun4i_usb_phy0_set_vbus_detect(phy0, 1);
 			mutex_unlock(&phy0->mutex);
 		}
+
+		/* Re-route PHY0 if necessary */
+		if (data->cfg->phy0_dual_route)
+			sun4i_usb_phy0_reroute(data, id_det);
 	}
 
 	if (vbus_notify)
@@ -700,7 +713,7 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev)
 			return PTR_ERR(phy->reset);
 		}
 
-		if (i) { /* No pmu for usbc0 */
+		if (i || data->cfg->phy0_dual_route) { /* No pmu for musb */
 			snprintf(name, sizeof(name), "pmu%d", i);
 			res = platform_get_resource_byname(pdev,
 							IORESOURCE_MEM, name);
@@ -823,8 +836,10 @@ static const struct sun4i_usb_phy_cfg sun8i_h3_cfg = {
 	.num_phys = 4,
 	.type = sun8i_h3_phy,
 	.disc_thresh = 3,
+	.phyctl_offset = REG_PHYCTL_A33,
 	.dedicated_clocks = true,
 	.enable_pmu_unk1 = true,
+	.phy0_dual_route = true,
 };
 
 static const struct sun4i_usb_phy_cfg sun8i_v3s_cfg = {
@@ -843,6 +858,7 @@ static const struct sun4i_usb_phy_cfg sun50i_a64_cfg = {
 	.phyctl_offset = REG_PHYCTL_A33,
 	.dedicated_clocks = true,
 	.enable_pmu_unk1 = true,
+	.phy0_dual_route = true,
 };
 
 static const struct of_device_id sun4i_usb_phy_of_match[] = {

+ 2 - 0
drivers/staging/Kconfig

@@ -104,4 +104,6 @@ source "drivers/staging/vc04_services/Kconfig"
 
 source "drivers/staging/bcm2835-audio/Kconfig"
 
+source "drivers/staging/typec/Kconfig"
+
 endif # STAGING

+ 1 - 1
drivers/staging/Makefile

@@ -1,6 +1,7 @@
 # Makefile for staging directory
 
 obj-y				+= media/
+obj-y				+= typec/
 obj-$(CONFIG_PRISM2_USB)	+= wlan-ng/
 obj-$(CONFIG_COMEDI)		+= comedi/
 obj-$(CONFIG_FB_OLPC_DCON)	+= olpc_dcon/
@@ -41,4 +42,3 @@ obj-$(CONFIG_KS7010)		+= ks7010/
 obj-$(CONFIG_GREYBUS)		+= greybus/
 obj-$(CONFIG_BCM2835_VCHIQ)	+= vc04_services/
 obj-$(CONFIG_SND_BCM2835)	+= bcm2835-audio/
-

+ 24 - 0
drivers/staging/typec/Kconfig

@@ -0,0 +1,24 @@
+menu "USB Power Delivery and Type-C drivers"
+
+config TYPEC_TCPM
+	tristate "USB Type-C Port Controller Manager"
+	depends on USB
+	select TYPEC
+	help
+	  The Type-C Port Controller Manager provides a USB PD and USB Type-C
+	  state machine for use with Type-C Port Controllers.
+
+if TYPEC_TCPM
+
+config TYPEC_TCPCI
+	tristate "Type-C Port Controller Interface driver"
+	depends on I2C
+	select REGMAP_I2C
+	help
+	  Type-C Port Controller driver for TCPCI-compliant controller.
+
+source "drivers/staging/typec/fusb302/Kconfig"
+
+endif
+
+endmenu

+ 3 - 0
drivers/staging/typec/Makefile

@@ -0,0 +1,3 @@
+obj-$(CONFIG_TYPEC_TCPM)	+= tcpm.o
+obj-$(CONFIG_TYPEC_TCPCI)	+= tcpci.o
+obj-y				+= fusb302/

+ 15 - 0
drivers/staging/typec/TODO

@@ -0,0 +1,15 @@
+tcpm:
+- Add documentation (at the very least for the API to low level drivers)
+- Split PD code into separate file
+- Check if it makes sense to use tracepoints instead of debugfs for debug logs
+- Implement Alternate Mode handling
+- Address "#if 0" code if not addressed with the above
+- Validate all comments marked with "XXX"; either address or remove comments
+- Add support for USB PD 3.0. While not mandatory, at least fast role swap
+  as well as authentication support would be very desirable.
+
+tcpci:
+- Test with real hardware
+
+Please send patches to Guenter Roeck <linux@roeck-us.net> and copy
+Heikki Krogerus <heikki.krogerus@linux.intel.com>.

+ 7 - 0
drivers/staging/typec/fusb302/Kconfig

@@ -0,0 +1,7 @@
+config TYPEC_FUSB302
+	tristate "Fairchild FUSB302 Type-C chip driver"
+	depends on I2C
+	help
+	  The Fairchild FUSB302 Type-C chip driver that works with
+	  Type-C Port Controller Manager to provide USB PD and USB
+	  Type-C functionalities.

+ 1 - 0
drivers/staging/typec/fusb302/Makefile

@@ -0,0 +1 @@
+obj-$(CONFIG_TYPEC_FUSB302)	+= fusb302.o

+ 6 - 0
drivers/staging/typec/fusb302/TODO

@@ -0,0 +1,6 @@
+fusb302:
+- Find a better logging scheme, at least not having the same debugging/logging
+  code replicated here and in tcpm
+- Find a non-hacky way to coordinate between PM and I2C access
+- Documentation? The FUSB302 datasheet provides information on the chip to help
+  understand the code. But it may still be helpful to have a documentation.

+ 1815 - 0
drivers/staging/typec/fusb302/fusb302.c

@@ -0,0 +1,1815 @@
+/*
+ * Copyright 2016-2017 Google, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * Fairchild FUSB302 Type-C Chip Driver
+ */
+
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_device.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/proc_fs.h>
+#include <linux/regulator/consumer.h>
+#include <linux/sched/clock.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/usb/typec.h>
+#include <linux/workqueue.h>
+
+#include "fusb302_reg.h"
+#include "../tcpm.h"
+#include "../pd.h"
+
+/*
+ * When the device is SNK, BC_LVL interrupt is used to monitor cc pins
+ * for the current capability offered by the SRC. As FUSB302 chip fires
+ * the BC_LVL interrupt on PD signalings, cc lvl should be handled after
+ * a delay to avoid measuring on PD activities. The delay is slightly
+ * longer than PD_T_PD_DEBPUNCE (10-20ms).
+ */
+#define T_BC_LVL_DEBOUNCE_DELAY_MS 30
+
+enum toggling_mode {
+	TOGGLINE_MODE_OFF,
+	TOGGLING_MODE_DRP,
+	TOGGLING_MODE_SNK,
+	TOGGLING_MODE_SRC,
+};
+
+static const char * const toggling_mode_name[] = {
+	[TOGGLINE_MODE_OFF]	= "toggling_OFF",
+	[TOGGLING_MODE_DRP]	= "toggling_DRP",
+	[TOGGLING_MODE_SNK]	= "toggling_SNK",
+	[TOGGLING_MODE_SRC]	= "toggling_SRC",
+};
+
+enum src_current_status {
+	SRC_CURRENT_DEFAULT,
+	SRC_CURRENT_MEDIUM,
+	SRC_CURRENT_HIGH,
+};
+
+static const u8 ra_mda_value[] = {
+	[SRC_CURRENT_DEFAULT] = 4,	/* 210mV */
+	[SRC_CURRENT_MEDIUM] = 9,	/* 420mV */
+	[SRC_CURRENT_HIGH] = 18,	/* 798mV */
+};
+
+static const u8 rd_mda_value[] = {
+	[SRC_CURRENT_DEFAULT] = 38,	/* 1638mV */
+	[SRC_CURRENT_MEDIUM] = 38,	/* 1638mV */
+	[SRC_CURRENT_HIGH] = 61,	/* 2604mV */
+};
+
+#define LOG_BUFFER_ENTRIES	1024
+#define LOG_BUFFER_ENTRY_SIZE	128
+
+struct fusb302_chip {
+	struct device *dev;
+	struct i2c_client *i2c_client;
+	struct tcpm_port *tcpm_port;
+	struct tcpc_dev tcpc_dev;
+
+	struct regulator *vbus;
+
+	int gpio_int_n;
+	int gpio_int_n_irq;
+
+	struct workqueue_struct *wq;
+	struct delayed_work bc_lvl_handler;
+
+	atomic_t pm_suspend;
+	atomic_t i2c_busy;
+
+	/* lock for sharing chip states */
+	struct mutex lock;
+
+	/* chip status */
+	enum toggling_mode toggling_mode;
+	enum src_current_status src_current_status;
+	bool intr_togdone;
+	bool intr_bc_lvl;
+	bool intr_comp_chng;
+
+	/* port status */
+	bool pull_up;
+	bool vconn_on;
+	bool vbus_on;
+	bool charge_on;
+	bool vbus_present;
+	enum typec_cc_polarity cc_polarity;
+	enum typec_cc_status cc1;
+	enum typec_cc_status cc2;
+
+#ifdef CONFIG_DEBUG_FS
+	struct dentry *dentry;
+	/* lock for log buffer access */
+	struct mutex logbuffer_lock;
+	int logbuffer_head;
+	int logbuffer_tail;
+	u8 *logbuffer[LOG_BUFFER_ENTRIES];
+#endif
+};
+
+/*
+ * Logging
+ */
+
+#ifdef CONFIG_DEBUG_FS
+
+static bool fusb302_log_full(struct fusb302_chip *chip)
+{
+	return chip->logbuffer_tail ==
+		(chip->logbuffer_head + 1) % LOG_BUFFER_ENTRIES;
+}
+
+static void _fusb302_log(struct fusb302_chip *chip, const char *fmt,
+			 va_list args)
+{
+	char tmpbuffer[LOG_BUFFER_ENTRY_SIZE];
+	u64 ts_nsec = local_clock();
+	unsigned long rem_nsec;
+
+	if (!chip->logbuffer[chip->logbuffer_head]) {
+		chip->logbuffer[chip->logbuffer_head] =
+				kzalloc(LOG_BUFFER_ENTRY_SIZE, GFP_KERNEL);
+		if (!chip->logbuffer[chip->logbuffer_head])
+			return;
+	}
+
+	vsnprintf(tmpbuffer, sizeof(tmpbuffer), fmt, args);
+
+	mutex_lock(&chip->logbuffer_lock);
+
+	if (fusb302_log_full(chip)) {
+		chip->logbuffer_head = max(chip->logbuffer_head - 1, 0);
+		strlcpy(tmpbuffer, "overflow", sizeof(tmpbuffer));
+	}
+
+	if (chip->logbuffer_head < 0 ||
+	    chip->logbuffer_head >= LOG_BUFFER_ENTRIES) {
+		dev_warn(chip->dev,
+			 "Bad log buffer index %d\n", chip->logbuffer_head);
+		goto abort;
+	}
+
+	if (!chip->logbuffer[chip->logbuffer_head]) {
+		dev_warn(chip->dev,
+			 "Log buffer index %d is NULL\n", chip->logbuffer_head);
+		goto abort;
+	}
+
+	rem_nsec = do_div(ts_nsec, 1000000000);
+	scnprintf(chip->logbuffer[chip->logbuffer_head],
+		  LOG_BUFFER_ENTRY_SIZE, "[%5lu.%06lu] %s",
+		  (unsigned long)ts_nsec, rem_nsec / 1000,
+		  tmpbuffer);
+	chip->logbuffer_head = (chip->logbuffer_head + 1) % LOG_BUFFER_ENTRIES;
+
+abort:
+	mutex_unlock(&chip->logbuffer_lock);
+}
+
+static void fusb302_log(struct fusb302_chip *chip, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	_fusb302_log(chip, fmt, args);
+	va_end(args);
+}
+
+static int fusb302_seq_show(struct seq_file *s, void *v)
+{
+	struct fusb302_chip *chip = (struct fusb302_chip *)s->private;
+	int tail;
+
+	mutex_lock(&chip->logbuffer_lock);
+	tail = chip->logbuffer_tail;
+	while (tail != chip->logbuffer_head) {
+		seq_printf(s, "%s\n", chip->logbuffer[tail]);
+		tail = (tail + 1) % LOG_BUFFER_ENTRIES;
+	}
+	if (!seq_has_overflowed(s))
+		chip->logbuffer_tail = tail;
+	mutex_unlock(&chip->logbuffer_lock);
+
+	return 0;
+}
+
+static int fusb302_debug_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, fusb302_seq_show, inode->i_private);
+}
+
+static const struct file_operations fusb302_debug_operations = {
+	.open		= fusb302_debug_open,
+	.llseek		= seq_lseek,
+	.read		= seq_read,
+	.release	= single_release,
+};
+
+static struct dentry *rootdir;
+
+static int fusb302_debugfs_init(struct fusb302_chip *chip)
+{
+	mutex_init(&chip->logbuffer_lock);
+	if (!rootdir) {
+		rootdir = debugfs_create_dir("fusb302", NULL);
+		if (!rootdir)
+			return -ENOMEM;
+	}
+
+	chip->dentry = debugfs_create_file(dev_name(chip->dev),
+					   S_IFREG | 0444, rootdir,
+					   chip, &fusb302_debug_operations);
+
+	return 0;
+}
+
+static void fusb302_debugfs_exit(struct fusb302_chip *chip)
+{
+	debugfs_remove(chip->dentry);
+}
+
+#else
+
+static void fusb302_log(const struct fusb302_chip *chip,
+			const char *fmt, ...) { }
+static int fusb302_debugfs_init(const struct fusb302_chip *chip) { return 0; }
+static void fusb302_debugfs_exit(const struct fusb302_chip *chip) { }
+
+#endif
+
+#define FUSB302_RESUME_RETRY 10
+#define FUSB302_RESUME_RETRY_SLEEP 50
+static int fusb302_i2c_write(struct fusb302_chip *chip,
+			     u8 address, u8 data)
+{
+	int retry_cnt;
+	int ret = 0;
+
+	atomic_set(&chip->i2c_busy, 1);
+	for (retry_cnt = 0; retry_cnt < FUSB302_RESUME_RETRY; retry_cnt++) {
+		if (atomic_read(&chip->pm_suspend)) {
+			pr_err("fusb302_i2c: pm suspend, retry %d/%d\n",
+			       retry_cnt + 1, FUSB302_RESUME_RETRY);
+			msleep(FUSB302_RESUME_RETRY_SLEEP);
+		} else {
+			break;
+		}
+	}
+	ret = i2c_smbus_write_byte_data(chip->i2c_client, address, data);
+	if (ret < 0)
+		fusb302_log(chip, "cannot write 0x%02x to 0x%02x, ret=%d",
+			    data, address, ret);
+	atomic_set(&chip->i2c_busy, 0);
+
+	return ret;
+}
+
+static int fusb302_i2c_block_write(struct fusb302_chip *chip, u8 address,
+				   u8 length, const u8 *data)
+{
+	int retry_cnt;
+	int ret = 0;
+
+	if (length <= 0)
+		return ret;
+	atomic_set(&chip->i2c_busy, 1);
+	for (retry_cnt = 0; retry_cnt < FUSB302_RESUME_RETRY; retry_cnt++) {
+		if (atomic_read(&chip->pm_suspend)) {
+			pr_err("fusb302_i2c: pm suspend, retry %d/%d\n",
+			       retry_cnt + 1, FUSB302_RESUME_RETRY);
+			msleep(FUSB302_RESUME_RETRY_SLEEP);
+		} else {
+			break;
+		}
+	}
+	ret = i2c_smbus_write_i2c_block_data(chip->i2c_client, address,
+					     length, data);
+	if (ret < 0)
+		fusb302_log(chip, "cannot block write 0x%02x, len=%d, ret=%d",
+			    address, length, ret);
+	atomic_set(&chip->i2c_busy, 0);
+
+	return ret;
+}
+
+static int fusb302_i2c_read(struct fusb302_chip *chip,
+			    u8 address, u8 *data)
+{
+	int retry_cnt;
+	int ret = 0;
+
+	atomic_set(&chip->i2c_busy, 1);
+	for (retry_cnt = 0; retry_cnt < FUSB302_RESUME_RETRY; retry_cnt++) {
+		if (atomic_read(&chip->pm_suspend)) {
+			pr_err("fusb302_i2c: pm suspend, retry %d/%d\n",
+			       retry_cnt + 1, FUSB302_RESUME_RETRY);
+			msleep(FUSB302_RESUME_RETRY_SLEEP);
+		} else {
+			break;
+		}
+	}
+	ret = i2c_smbus_read_byte_data(chip->i2c_client, address);
+	*data = (u8)ret;
+	if (ret < 0)
+		fusb302_log(chip, "cannot read %02x, ret=%d", address, ret);
+	atomic_set(&chip->i2c_busy, 0);
+
+	return ret;
+}
+
+static int fusb302_i2c_block_read(struct fusb302_chip *chip, u8 address,
+				  u8 length, u8 *data)
+{
+	int retry_cnt;
+	int ret = 0;
+
+	if (length <= 0)
+		return ret;
+	atomic_set(&chip->i2c_busy, 1);
+	for (retry_cnt = 0; retry_cnt < FUSB302_RESUME_RETRY; retry_cnt++) {
+		if (atomic_read(&chip->pm_suspend)) {
+			pr_err("fusb302_i2c: pm suspend, retry %d/%d\n",
+			       retry_cnt + 1, FUSB302_RESUME_RETRY);
+			msleep(FUSB302_RESUME_RETRY_SLEEP);
+		} else {
+			break;
+		}
+	}
+	ret = i2c_smbus_read_i2c_block_data(chip->i2c_client, address,
+					    length, data);
+	if (ret < 0) {
+		fusb302_log(chip, "cannot block read 0x%02x, len=%d, ret=%d",
+			    address, length, ret);
+		return ret;
+	}
+	if (ret != length) {
+		fusb302_log(chip, "only read %d/%d bytes from 0x%02x",
+			    ret, length, address);
+		return -EIO;
+	}
+	atomic_set(&chip->i2c_busy, 0);
+
+	return ret;
+}
+
+static int fusb302_i2c_mask_write(struct fusb302_chip *chip, u8 address,
+				  u8 mask, u8 value)
+{
+	int ret = 0;
+	u8 data;
+
+	ret = fusb302_i2c_read(chip, address, &data);
+	if (ret < 0)
+		return ret;
+	data &= ~mask;
+	data |= value;
+	ret = fusb302_i2c_write(chip, address, data);
+	if (ret < 0)
+		return ret;
+
+	return ret;
+}
+
+static int fusb302_i2c_set_bits(struct fusb302_chip *chip, u8 address,
+				u8 set_bits)
+{
+	return fusb302_i2c_mask_write(chip, address, 0x00, set_bits);
+}
+
+static int fusb302_i2c_clear_bits(struct fusb302_chip *chip, u8 address,
+				  u8 clear_bits)
+{
+	return fusb302_i2c_mask_write(chip, address, clear_bits, 0x00);
+}
+
+static int fusb302_sw_reset(struct fusb302_chip *chip)
+{
+	int ret = 0;
+
+	ret = fusb302_i2c_write(chip, FUSB_REG_RESET,
+				FUSB_REG_RESET_SW_RESET);
+	if (ret < 0)
+		fusb302_log(chip, "cannot sw reset the chip, ret=%d", ret);
+	else
+		fusb302_log(chip, "sw reset");
+
+	return ret;
+}
+
+static int fusb302_enable_tx_auto_retries(struct fusb302_chip *chip)
+{
+	int ret = 0;
+
+	ret = fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL3,
+				   FUSB_REG_CONTROL3_N_RETRIES_3 |
+				   FUSB_REG_CONTROL3_AUTO_RETRY);
+
+	return ret;
+}
+
+/*
+ * initialize interrupt on the chip
+ * - unmasked interrupt: VBUS_OK
+ */
+static int fusb302_init_interrupt(struct fusb302_chip *chip)
+{
+	int ret = 0;
+
+	ret = fusb302_i2c_write(chip, FUSB_REG_MASK,
+				0xFF & ~FUSB_REG_MASK_VBUSOK);
+	if (ret < 0)
+		return ret;
+	ret = fusb302_i2c_write(chip, FUSB_REG_MASKA, 0xFF);
+	if (ret < 0)
+		return ret;
+	ret = fusb302_i2c_write(chip, FUSB_REG_MASKB, 0xFF);
+	if (ret < 0)
+		return ret;
+	ret = fusb302_i2c_clear_bits(chip, FUSB_REG_CONTROL0,
+				     FUSB_REG_CONTROL0_INT_MASK);
+	if (ret < 0)
+		return ret;
+
+	return ret;
+}
+
+static int fusb302_set_power_mode(struct fusb302_chip *chip, u8 power_mode)
+{
+	int ret = 0;
+
+	ret = fusb302_i2c_write(chip, FUSB_REG_POWER, power_mode);
+
+	return ret;
+}
+
+static int tcpm_init(struct tcpc_dev *dev)
+{
+	struct fusb302_chip *chip = container_of(dev, struct fusb302_chip,
+						 tcpc_dev);
+	int ret = 0;
+	u8 data;
+
+	ret = fusb302_sw_reset(chip);
+	if (ret < 0)
+		return ret;
+	ret = fusb302_enable_tx_auto_retries(chip);
+	if (ret < 0)
+		return ret;
+	ret = fusb302_init_interrupt(chip);
+	if (ret < 0)
+		return ret;
+	ret = fusb302_set_power_mode(chip, FUSB_REG_POWER_PWR_ALL);
+	if (ret < 0)
+		return ret;
+	ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &data);
+	if (ret < 0)
+		return ret;
+	chip->vbus_present = !!(FUSB_REG_STATUS0 & FUSB_REG_STATUS0_VBUSOK);
+	ret = fusb302_i2c_read(chip, FUSB_REG_DEVICE_ID, &data);
+	if (ret < 0)
+		return ret;
+	fusb302_log(chip, "fusb302 device ID: 0x%02x", data);
+
+	return ret;
+}
+
+static int tcpm_get_vbus(struct tcpc_dev *dev)
+{
+	struct fusb302_chip *chip = container_of(dev, struct fusb302_chip,
+						 tcpc_dev);
+	int ret = 0;
+
+	mutex_lock(&chip->lock);
+	ret = chip->vbus_present ? 1 : 0;
+	mutex_unlock(&chip->lock);
+
+	return ret;
+}
+
+static int fusb302_set_cc_pull(struct fusb302_chip *chip,
+			       bool pull_up, bool pull_down)
+{
+	int ret = 0;
+	u8 data = 0x00;
+	u8 mask = FUSB_REG_SWITCHES0_CC1_PU_EN |
+		  FUSB_REG_SWITCHES0_CC2_PU_EN |
+		  FUSB_REG_SWITCHES0_CC1_PD_EN |
+		  FUSB_REG_SWITCHES0_CC2_PD_EN;
+
+	if (pull_up)
+		data |= (chip->cc_polarity == TYPEC_POLARITY_CC1) ?
+			FUSB_REG_SWITCHES0_CC1_PU_EN :
+			FUSB_REG_SWITCHES0_CC2_PU_EN;
+	if (pull_down)
+		data |= FUSB_REG_SWITCHES0_CC1_PD_EN |
+			FUSB_REG_SWITCHES0_CC2_PD_EN;
+	ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES0,
+				     mask, data);
+	if (ret < 0)
+		return ret;
+	chip->pull_up = pull_up;
+
+	return ret;
+}
+
+static int fusb302_set_src_current(struct fusb302_chip *chip,
+				   enum src_current_status status)
+{
+	int ret = 0;
+
+	chip->src_current_status = status;
+	switch (status) {
+	case SRC_CURRENT_DEFAULT:
+		ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL0,
+					     FUSB_REG_CONTROL0_HOST_CUR_MASK,
+					     FUSB_REG_CONTROL0_HOST_CUR_DEF);
+		break;
+	case SRC_CURRENT_MEDIUM:
+		ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL0,
+					     FUSB_REG_CONTROL0_HOST_CUR_MASK,
+					     FUSB_REG_CONTROL0_HOST_CUR_MED);
+		break;
+	case SRC_CURRENT_HIGH:
+		ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL0,
+					     FUSB_REG_CONTROL0_HOST_CUR_MASK,
+					     FUSB_REG_CONTROL0_HOST_CUR_HIGH);
+		break;
+	default:
+		break;
+	}
+
+	return ret;
+}
+
+static int fusb302_set_toggling(struct fusb302_chip *chip,
+				enum toggling_mode mode)
+{
+	int ret = 0;
+
+	/* first disable toggling */
+	ret = fusb302_i2c_clear_bits(chip, FUSB_REG_CONTROL2,
+				     FUSB_REG_CONTROL2_TOGGLE);
+	if (ret < 0)
+		return ret;
+	/* mask interrupts for SRC or SNK */
+	ret = fusb302_i2c_set_bits(chip, FUSB_REG_MASK,
+				   FUSB_REG_MASK_BC_LVL |
+				   FUSB_REG_MASK_COMP_CHNG);
+	if (ret < 0)
+		return ret;
+	chip->intr_bc_lvl = false;
+	chip->intr_comp_chng = false;
+	/* configure toggling mode: none/snk/src/drp */
+	switch (mode) {
+	case TOGGLINE_MODE_OFF:
+		ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL2,
+					     FUSB_REG_CONTROL2_MODE_MASK,
+					     FUSB_REG_CONTROL2_MODE_NONE);
+		if (ret < 0)
+			return ret;
+		break;
+	case TOGGLING_MODE_SNK:
+		ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL2,
+					     FUSB_REG_CONTROL2_MODE_MASK,
+					     FUSB_REG_CONTROL2_MODE_UFP);
+		if (ret < 0)
+			return ret;
+		break;
+	case TOGGLING_MODE_SRC:
+		ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL2,
+					     FUSB_REG_CONTROL2_MODE_MASK,
+					     FUSB_REG_CONTROL2_MODE_DFP);
+		if (ret < 0)
+			return ret;
+		break;
+	case TOGGLING_MODE_DRP:
+		ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL2,
+					     FUSB_REG_CONTROL2_MODE_MASK,
+					     FUSB_REG_CONTROL2_MODE_DRP);
+		if (ret < 0)
+			return ret;
+		break;
+	default:
+		break;
+	}
+
+	if (mode == TOGGLINE_MODE_OFF) {
+		/* mask TOGDONE interrupt */
+		ret = fusb302_i2c_set_bits(chip, FUSB_REG_MASKA,
+					   FUSB_REG_MASKA_TOGDONE);
+		if (ret < 0)
+			return ret;
+		chip->intr_togdone = false;
+	} else {
+		/* unmask TOGDONE interrupt */
+		ret = fusb302_i2c_clear_bits(chip, FUSB_REG_MASKA,
+					     FUSB_REG_MASKA_TOGDONE);
+		if (ret < 0)
+			return ret;
+		chip->intr_togdone = true;
+		/* start toggling */
+		ret = fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL2,
+					   FUSB_REG_CONTROL2_TOGGLE);
+		if (ret < 0)
+			return ret;
+		/* during toggling, consider cc as Open */
+		chip->cc1 = TYPEC_CC_OPEN;
+		chip->cc2 = TYPEC_CC_OPEN;
+	}
+	chip->toggling_mode = mode;
+
+	return ret;
+}
+
+static const char * const typec_cc_status_name[] = {
+	[TYPEC_CC_OPEN]		= "Open",
+	[TYPEC_CC_RA]		= "Ra",
+	[TYPEC_CC_RD]		= "Rd",
+	[TYPEC_CC_RP_DEF]	= "Rp-def",
+	[TYPEC_CC_RP_1_5]	= "Rp-1.5",
+	[TYPEC_CC_RP_3_0]	= "Rp-3.0",
+};
+
+static const enum src_current_status cc_src_current[] = {
+	[TYPEC_CC_OPEN]		= SRC_CURRENT_DEFAULT,
+	[TYPEC_CC_RA]		= SRC_CURRENT_DEFAULT,
+	[TYPEC_CC_RD]		= SRC_CURRENT_DEFAULT,
+	[TYPEC_CC_RP_DEF]	= SRC_CURRENT_DEFAULT,
+	[TYPEC_CC_RP_1_5]	= SRC_CURRENT_MEDIUM,
+	[TYPEC_CC_RP_3_0]	= SRC_CURRENT_HIGH,
+};
+
+static int tcpm_set_cc(struct tcpc_dev *dev, enum typec_cc_status cc)
+{
+	struct fusb302_chip *chip = container_of(dev, struct fusb302_chip,
+						 tcpc_dev);
+	int ret = 0;
+	bool pull_up, pull_down;
+	u8 rd_mda;
+
+	mutex_lock(&chip->lock);
+	switch (cc) {
+	case TYPEC_CC_OPEN:
+		pull_up = false;
+		pull_down = false;
+		break;
+	case TYPEC_CC_RD:
+		pull_up = false;
+		pull_down = true;
+		break;
+	case TYPEC_CC_RP_DEF:
+	case TYPEC_CC_RP_1_5:
+	case TYPEC_CC_RP_3_0:
+		pull_up = true;
+		pull_down = false;
+		break;
+	default:
+		fusb302_log(chip, "unsupported cc value %s",
+			    typec_cc_status_name[cc]);
+		ret = -EINVAL;
+		goto done;
+	}
+	ret = fusb302_set_toggling(chip, TOGGLINE_MODE_OFF);
+	if (ret < 0) {
+		fusb302_log(chip, "cannot stop toggling, ret=%d", ret);
+		goto done;
+	}
+	ret = fusb302_set_cc_pull(chip, pull_up, pull_down);
+	if (ret < 0) {
+		fusb302_log(chip,
+			    "cannot set cc pulling up %s, down %s, ret = %d",
+			    pull_up ? "True" : "False",
+			    pull_down ? "True" : "False",
+			    ret);
+		goto done;
+	}
+	/* reset the cc status */
+	chip->cc1 = TYPEC_CC_OPEN;
+	chip->cc2 = TYPEC_CC_OPEN;
+	/* adjust current for SRC */
+	if (pull_up) {
+		ret = fusb302_set_src_current(chip, cc_src_current[cc]);
+		if (ret < 0) {
+			fusb302_log(chip, "cannot set src current %s, ret=%d",
+				    typec_cc_status_name[cc], ret);
+			goto done;
+		}
+	}
+	/* enable/disable interrupts, BC_LVL for SNK and COMP_CHNG for SRC */
+	if (pull_up) {
+		rd_mda = rd_mda_value[cc_src_current[cc]];
+		ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, rd_mda);
+		if (ret < 0) {
+			fusb302_log(chip,
+				    "cannot set SRC measure value, ret=%d",
+				    ret);
+			goto done;
+		}
+		ret = fusb302_i2c_mask_write(chip, FUSB_REG_MASK,
+					     FUSB_REG_MASK_BC_LVL |
+					     FUSB_REG_MASK_COMP_CHNG,
+					     FUSB_REG_MASK_COMP_CHNG);
+		if (ret < 0) {
+			fusb302_log(chip, "cannot set SRC interrupt, ret=%d",
+				    ret);
+			goto done;
+		}
+		chip->intr_bc_lvl = false;
+		chip->intr_comp_chng = true;
+	}
+	if (pull_down) {
+		ret = fusb302_i2c_mask_write(chip, FUSB_REG_MASK,
+					     FUSB_REG_MASK_BC_LVL |
+					     FUSB_REG_MASK_COMP_CHNG,
+					     FUSB_REG_MASK_BC_LVL);
+		if (ret < 0) {
+			fusb302_log(chip, "cannot set SRC interrupt, ret=%d",
+				    ret);
+			goto done;
+		}
+		chip->intr_bc_lvl = true;
+		chip->intr_comp_chng = false;
+	}
+	fusb302_log(chip, "cc := %s", typec_cc_status_name[cc]);
+done:
+	mutex_unlock(&chip->lock);
+
+	return ret;
+}
+
+static int tcpm_get_cc(struct tcpc_dev *dev, enum typec_cc_status *cc1,
+		       enum typec_cc_status *cc2)
+{
+	struct fusb302_chip *chip = container_of(dev, struct fusb302_chip,
+						 tcpc_dev);
+
+	mutex_lock(&chip->lock);
+	*cc1 = chip->cc1;
+	*cc2 = chip->cc2;
+	fusb302_log(chip, "cc1=%s, cc2=%s", typec_cc_status_name[*cc1],
+		    typec_cc_status_name[*cc2]);
+	mutex_unlock(&chip->lock);
+
+	return 0;
+}
+
+static int tcpm_set_polarity(struct tcpc_dev *dev,
+			     enum typec_cc_polarity polarity)
+{
+	return 0;
+}
+
+static int tcpm_set_vconn(struct tcpc_dev *dev, bool on)
+{
+	struct fusb302_chip *chip = container_of(dev, struct fusb302_chip,
+						 tcpc_dev);
+	int ret = 0;
+	u8 switches0_data = 0x00;
+	u8 switches0_mask = FUSB_REG_SWITCHES0_VCONN_CC1 |
+			    FUSB_REG_SWITCHES0_VCONN_CC2;
+
+	mutex_lock(&chip->lock);
+	if (chip->vconn_on == on) {
+		fusb302_log(chip, "vconn is already %s", on ? "On" : "Off");
+		goto done;
+	}
+	if (on) {
+		switches0_data = (chip->cc_polarity == TYPEC_POLARITY_CC1) ?
+				 FUSB_REG_SWITCHES0_VCONN_CC2 :
+				 FUSB_REG_SWITCHES0_VCONN_CC1;
+	}
+	ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES0,
+				     switches0_mask, switches0_data);
+	if (ret < 0)
+		goto done;
+	chip->vconn_on = on;
+	fusb302_log(chip, "vconn := %s", on ? "On" : "Off");
+done:
+	mutex_unlock(&chip->lock);
+
+	return ret;
+}
+
+static int tcpm_set_vbus(struct tcpc_dev *dev, bool on, bool charge)
+{
+	struct fusb302_chip *chip = container_of(dev, struct fusb302_chip,
+						 tcpc_dev);
+	int ret = 0;
+
+	mutex_lock(&chip->lock);
+	if (chip->vbus_on == on) {
+		fusb302_log(chip, "vbus is already %s", on ? "On" : "Off");
+	} else {
+		if (on)
+			ret = regulator_enable(chip->vbus);
+		else
+			ret = regulator_disable(chip->vbus);
+		if (ret < 0) {
+			fusb302_log(chip, "cannot %s vbus regulator, ret=%d",
+				    on ? "enable" : "disable", ret);
+			goto done;
+		}
+		chip->vbus_on = on;
+		fusb302_log(chip, "vbus := %s", on ? "On" : "Off");
+	}
+	if (chip->charge_on == charge)
+		fusb302_log(chip, "charge is already %s",
+			    charge ? "On" : "Off");
+	else
+		chip->charge_on = charge;
+
+done:
+	mutex_unlock(&chip->lock);
+
+	return ret;
+}
+
+static int tcpm_set_current_limit(struct tcpc_dev *dev, u32 max_ma, u32 mv)
+{
+	struct fusb302_chip *chip = container_of(dev, struct fusb302_chip,
+						 tcpc_dev);
+
+	fusb302_log(chip, "current limit: %d ma, %d mv (not implemented)",
+		    max_ma, mv);
+
+	return 0;
+}
+
+static int fusb302_pd_tx_flush(struct fusb302_chip *chip)
+{
+	return fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL0,
+				    FUSB_REG_CONTROL0_TX_FLUSH);
+}
+
+static int fusb302_pd_rx_flush(struct fusb302_chip *chip)
+{
+	return fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL1,
+				    FUSB_REG_CONTROL1_RX_FLUSH);
+}
+
+static int fusb302_pd_set_auto_goodcrc(struct fusb302_chip *chip, bool on)
+{
+	if (on)
+		return fusb302_i2c_set_bits(chip, FUSB_REG_SWITCHES1,
+					    FUSB_REG_SWITCHES1_AUTO_GCRC);
+	return fusb302_i2c_clear_bits(chip, FUSB_REG_SWITCHES1,
+					    FUSB_REG_SWITCHES1_AUTO_GCRC);
+}
+
+static int fusb302_pd_set_interrupts(struct fusb302_chip *chip, bool on)
+{
+	int ret = 0;
+	u8 mask_interrupts = FUSB_REG_MASK_COLLISION;
+	u8 maska_interrupts = FUSB_REG_MASKA_RETRYFAIL |
+			      FUSB_REG_MASKA_HARDSENT |
+			      FUSB_REG_MASKA_TX_SUCCESS |
+			      FUSB_REG_MASKA_HARDRESET;
+	u8 maskb_interrupts = FUSB_REG_MASKB_GCRCSENT;
+
+	ret = on ?
+		fusb302_i2c_clear_bits(chip, FUSB_REG_MASK, mask_interrupts) :
+		fusb302_i2c_set_bits(chip, FUSB_REG_MASK, mask_interrupts);
+	if (ret < 0)
+		return ret;
+	ret = on ?
+		fusb302_i2c_clear_bits(chip, FUSB_REG_MASKA, maska_interrupts) :
+		fusb302_i2c_set_bits(chip, FUSB_REG_MASKA, maska_interrupts);
+	if (ret < 0)
+		return ret;
+	ret = on ?
+		fusb302_i2c_clear_bits(chip, FUSB_REG_MASKB, maskb_interrupts) :
+		fusb302_i2c_set_bits(chip, FUSB_REG_MASKB, maskb_interrupts);
+	return ret;
+}
+
+static int tcpm_set_pd_rx(struct tcpc_dev *dev, bool on)
+{
+	struct fusb302_chip *chip = container_of(dev, struct fusb302_chip,
+						 tcpc_dev);
+	int ret = 0;
+
+	mutex_lock(&chip->lock);
+	ret = fusb302_pd_rx_flush(chip);
+	if (ret < 0) {
+		fusb302_log(chip, "cannot flush pd rx buffer, ret=%d", ret);
+		goto done;
+	}
+	ret = fusb302_pd_tx_flush(chip);
+	if (ret < 0) {
+		fusb302_log(chip, "cannot flush pd tx buffer, ret=%d", ret);
+		goto done;
+	}
+	ret = fusb302_pd_set_auto_goodcrc(chip, on);
+	if (ret < 0) {
+		fusb302_log(chip, "cannot turn %s auto GCRC, ret=%d",
+			    on ? "on" : "off", ret);
+		goto done;
+	}
+	ret = fusb302_pd_set_interrupts(chip, on);
+	if (ret < 0) {
+		fusb302_log(chip, "cannot turn %s pd interrupts, ret=%d",
+			    on ? "on" : "off", ret);
+		goto done;
+	}
+	fusb302_log(chip, "pd := %s", on ? "on" : "off");
+done:
+	mutex_unlock(&chip->lock);
+
+	return ret;
+}
+
+static const char * const typec_role_name[] = {
+	[TYPEC_SINK]		= "Sink",
+	[TYPEC_SOURCE]		= "Source",
+};
+
+static const char * const typec_data_role_name[] = {
+	[TYPEC_DEVICE]		= "Device",
+	[TYPEC_HOST]		= "Host",
+};
+
+static int tcpm_set_roles(struct tcpc_dev *dev, bool attached,
+			  enum typec_role pwr, enum typec_data_role data)
+{
+	struct fusb302_chip *chip = container_of(dev, struct fusb302_chip,
+						 tcpc_dev);
+	int ret = 0;
+	u8 switches1_mask = FUSB_REG_SWITCHES1_POWERROLE |
+			    FUSB_REG_SWITCHES1_DATAROLE;
+	u8 switches1_data = 0x00;
+
+	mutex_lock(&chip->lock);
+	if (pwr == TYPEC_SOURCE)
+		switches1_data |= FUSB_REG_SWITCHES1_POWERROLE;
+	if (data == TYPEC_HOST)
+		switches1_data |= FUSB_REG_SWITCHES1_DATAROLE;
+	ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES1,
+				     switches1_mask, switches1_data);
+	if (ret < 0) {
+		fusb302_log(chip, "unable to set pd header %s, %s, ret=%d",
+			    typec_role_name[pwr], typec_data_role_name[data],
+			    ret);
+		goto done;
+	}
+	fusb302_log(chip, "pd header := %s, %s", typec_role_name[pwr],
+		    typec_data_role_name[data]);
+done:
+	mutex_unlock(&chip->lock);
+
+	return ret;
+}
+
+static int tcpm_start_drp_toggling(struct tcpc_dev *dev,
+				   enum typec_cc_status cc)
+{
+	struct fusb302_chip *chip = container_of(dev, struct fusb302_chip,
+						 tcpc_dev);
+	int ret = 0;
+
+	mutex_lock(&chip->lock);
+	ret = fusb302_set_src_current(chip, cc_src_current[cc]);
+	if (ret < 0) {
+		fusb302_log(chip, "unable to set src current %s, ret=%d",
+			    typec_cc_status_name[cc], ret);
+		goto done;
+	}
+	ret = fusb302_set_toggling(chip, TOGGLING_MODE_DRP);
+	if (ret < 0) {
+		fusb302_log(chip,
+			    "unable to start drp toggling, ret=%d", ret);
+		goto done;
+	}
+	fusb302_log(chip, "start drp toggling");
+done:
+	mutex_unlock(&chip->lock);
+
+	return ret;
+}
+
+static int fusb302_pd_send_message(struct fusb302_chip *chip,
+				   const struct pd_message *msg)
+{
+	int ret = 0;
+	u8 buf[40];
+	u8 pos = 0;
+	int len;
+
+	/* SOP tokens */
+	buf[pos++] = FUSB302_TKN_SYNC1;
+	buf[pos++] = FUSB302_TKN_SYNC1;
+	buf[pos++] = FUSB302_TKN_SYNC1;
+	buf[pos++] = FUSB302_TKN_SYNC2;
+
+	len = pd_header_cnt(msg->header) * 4;
+	/* plug 2 for header */
+	len += 2;
+	if (len > 0x1F) {
+		fusb302_log(chip,
+			    "PD message too long %d (incl. header)", len);
+		return -EINVAL;
+	}
+	/* packsym tells the FUSB302 chip that the next X bytes are payload */
+	buf[pos++] = FUSB302_TKN_PACKSYM | (len & 0x1F);
+	buf[pos++] = msg->header & 0xFF;
+	buf[pos++] = (msg->header >> 8) & 0xFF;
+
+	len -= 2;
+	memcpy(&buf[pos], msg->payload, len);
+	pos += len;
+
+	/* CRC */
+	buf[pos++] = FUSB302_TKN_JAMCRC;
+	/* EOP */
+	buf[pos++] = FUSB302_TKN_EOP;
+	/* turn tx off after sending message */
+	buf[pos++] = FUSB302_TKN_TXOFF;
+	/* start transmission */
+	buf[pos++] = FUSB302_TKN_TXON;
+
+	ret = fusb302_i2c_block_write(chip, FUSB_REG_FIFOS, pos, buf);
+	if (ret < 0)
+		return ret;
+	fusb302_log(chip, "sending PD message header: %x", msg->header);
+	fusb302_log(chip, "sending PD message len: %d", len);
+
+	return ret;
+}
+
+static int fusb302_pd_send_hardreset(struct fusb302_chip *chip)
+{
+	return fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL3,
+				    FUSB_REG_CONTROL3_SEND_HARDRESET);
+}
+
+static const char * const transmit_type_name[] = {
+	[TCPC_TX_SOP]			= "SOP",
+	[TCPC_TX_SOP_PRIME]		= "SOP'",
+	[TCPC_TX_SOP_PRIME_PRIME]	= "SOP''",
+	[TCPC_TX_SOP_DEBUG_PRIME]	= "DEBUG'",
+	[TCPC_TX_SOP_DEBUG_PRIME_PRIME]	= "DEBUG''",
+	[TCPC_TX_HARD_RESET]		= "HARD_RESET",
+	[TCPC_TX_CABLE_RESET]		= "CABLE_RESET",
+	[TCPC_TX_BIST_MODE_2]		= "BIST_MODE_2",
+};
+
+static int tcpm_pd_transmit(struct tcpc_dev *dev, enum tcpm_transmit_type type,
+			    const struct pd_message *msg)
+{
+	struct fusb302_chip *chip = container_of(dev, struct fusb302_chip,
+						 tcpc_dev);
+	int ret = 0;
+
+	mutex_lock(&chip->lock);
+	switch (type) {
+	case TCPC_TX_SOP:
+		ret = fusb302_pd_send_message(chip, msg);
+		if (ret < 0)
+			fusb302_log(chip,
+				    "cannot send PD message, ret=%d", ret);
+		break;
+	case TCPC_TX_HARD_RESET:
+		ret = fusb302_pd_send_hardreset(chip);
+		if (ret < 0)
+			fusb302_log(chip,
+				    "cannot send hardreset, ret=%d", ret);
+		break;
+	default:
+		fusb302_log(chip, "type %s not supported",
+			    transmit_type_name[type]);
+		ret = -EINVAL;
+	}
+	mutex_unlock(&chip->lock);
+
+	return ret;
+}
+
+static enum typec_cc_status fusb302_bc_lvl_to_cc(u8 bc_lvl)
+{
+	if (bc_lvl == FUSB_REG_STATUS0_BC_LVL_1230_MAX)
+		return TYPEC_CC_RP_3_0;
+	if (bc_lvl == FUSB_REG_STATUS0_BC_LVL_600_1230)
+		return TYPEC_CC_RP_1_5;
+	if (bc_lvl == FUSB_REG_STATUS0_BC_LVL_200_600)
+		return TYPEC_CC_RP_DEF;
+	return TYPEC_CC_OPEN;
+}
+
+static void fusb302_bc_lvl_handler_work(struct work_struct *work)
+{
+	struct fusb302_chip *chip = container_of(work, struct fusb302_chip,
+						 bc_lvl_handler.work);
+	int ret = 0;
+	u8 status0;
+	u8 bc_lvl;
+	enum typec_cc_status cc_status;
+
+	mutex_lock(&chip->lock);
+	if (!chip->intr_bc_lvl) {
+		fusb302_log(chip, "BC_LVL interrupt is turned off, abort");
+		goto done;
+	}
+	ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0);
+	if (ret < 0)
+		goto done;
+	fusb302_log(chip, "BC_LVL handler, status0=0x%02x", status0);
+	if (status0 & FUSB_REG_STATUS0_ACTIVITY) {
+		fusb302_log(chip, "CC activities detected, delay handling");
+		mod_delayed_work(chip->wq, &chip->bc_lvl_handler,
+				 msecs_to_jiffies(T_BC_LVL_DEBOUNCE_DELAY_MS));
+		goto done;
+	}
+	bc_lvl = status0 & FUSB_REG_STATUS0_BC_LVL_MASK;
+	cc_status = fusb302_bc_lvl_to_cc(bc_lvl);
+	if (chip->cc_polarity == TYPEC_POLARITY_CC1) {
+		if (chip->cc1 != cc_status) {
+			fusb302_log(chip, "cc1: %s -> %s",
+				    typec_cc_status_name[chip->cc1],
+				    typec_cc_status_name[cc_status]);
+			chip->cc1 = cc_status;
+			tcpm_cc_change(chip->tcpm_port);
+		}
+	} else {
+		if (chip->cc2 != cc_status) {
+			fusb302_log(chip, "cc2: %s -> %s",
+				    typec_cc_status_name[chip->cc2],
+				    typec_cc_status_name[cc_status]);
+			chip->cc2 = cc_status;
+			tcpm_cc_change(chip->tcpm_port);
+		}
+	}
+
+done:
+	mutex_unlock(&chip->lock);
+}
+
+#define PDO_FIXED_FLAGS \
+	(PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP | PDO_FIXED_USB_COMM)
+
+static const u32 src_pdo[] = {
+	PDO_FIXED(5000, 400, PDO_FIXED_FLAGS),
+};
+
+static const u32 snk_pdo[] = {
+	PDO_FIXED(5000, 400, PDO_FIXED_FLAGS),
+};
+
+static const struct tcpc_config fusb302_tcpc_config = {
+	.src_pdo = src_pdo,
+	.nr_src_pdo = ARRAY_SIZE(src_pdo),
+	.snk_pdo = snk_pdo,
+	.nr_snk_pdo = ARRAY_SIZE(snk_pdo),
+	.max_snk_mv = 9000,
+	.max_snk_ma = 3000,
+	.max_snk_mw = 27000,
+	.operating_snk_mw = 2500,
+	.type = TYPEC_PORT_DRP,
+	.default_role = TYPEC_SINK,
+	.alt_modes = NULL,
+};
+
+static void init_tcpc_dev(struct tcpc_dev *fusb302_tcpc_dev)
+{
+	fusb302_tcpc_dev->config = &fusb302_tcpc_config;
+	fusb302_tcpc_dev->init = tcpm_init;
+	fusb302_tcpc_dev->get_vbus = tcpm_get_vbus;
+	fusb302_tcpc_dev->set_cc = tcpm_set_cc;
+	fusb302_tcpc_dev->get_cc = tcpm_get_cc;
+	fusb302_tcpc_dev->set_polarity = tcpm_set_polarity;
+	fusb302_tcpc_dev->set_vconn = tcpm_set_vconn;
+	fusb302_tcpc_dev->set_vbus = tcpm_set_vbus;
+	fusb302_tcpc_dev->set_current_limit = tcpm_set_current_limit;
+	fusb302_tcpc_dev->set_pd_rx = tcpm_set_pd_rx;
+	fusb302_tcpc_dev->set_roles = tcpm_set_roles;
+	fusb302_tcpc_dev->start_drp_toggling = tcpm_start_drp_toggling;
+	fusb302_tcpc_dev->pd_transmit = tcpm_pd_transmit;
+	fusb302_tcpc_dev->mux = NULL;
+}
+
+static const char * const cc_polarity_name[] = {
+	[TYPEC_POLARITY_CC1]	= "Polarity_CC1",
+	[TYPEC_POLARITY_CC2]	= "Polarity_CC2",
+};
+
+static int fusb302_set_cc_polarity(struct fusb302_chip *chip,
+				   enum typec_cc_polarity cc_polarity)
+{
+	int ret = 0;
+	u8 switches0_mask = FUSB_REG_SWITCHES0_CC1_PU_EN |
+			    FUSB_REG_SWITCHES0_CC2_PU_EN |
+			    FUSB_REG_SWITCHES0_VCONN_CC1 |
+			    FUSB_REG_SWITCHES0_VCONN_CC2 |
+			    FUSB_REG_SWITCHES0_MEAS_CC1 |
+			    FUSB_REG_SWITCHES0_MEAS_CC2;
+	u8 switches0_data = 0x00;
+	u8 switches1_mask = FUSB_REG_SWITCHES1_TXCC1_EN |
+			    FUSB_REG_SWITCHES1_TXCC2_EN;
+	u8 switches1_data = 0x00;
+
+	if (cc_polarity == TYPEC_POLARITY_CC1) {
+		switches0_data = FUSB_REG_SWITCHES0_MEAS_CC1;
+		if (chip->vconn_on)
+			switches0_data |= FUSB_REG_SWITCHES0_VCONN_CC2;
+		if (chip->pull_up)
+			switches0_data |= FUSB_REG_SWITCHES0_CC1_PU_EN;
+		switches1_data = FUSB_REG_SWITCHES1_TXCC1_EN;
+	} else {
+		switches0_data = FUSB_REG_SWITCHES0_MEAS_CC2;
+		if (chip->vconn_on)
+			switches0_data |= FUSB_REG_SWITCHES0_VCONN_CC1;
+		if (chip->pull_up)
+			switches0_data |= FUSB_REG_SWITCHES0_CC2_PU_EN;
+		switches1_data = FUSB_REG_SWITCHES1_TXCC2_EN;
+	}
+	ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES0,
+				     switches0_mask, switches0_data);
+	if (ret < 0)
+		return ret;
+	ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES1,
+				     switches1_mask, switches1_data);
+	if (ret < 0)
+		return ret;
+	chip->cc_polarity = cc_polarity;
+
+	return ret;
+}
+
+static int fusb302_handle_togdone_snk(struct fusb302_chip *chip,
+				      u8 togdone_result)
+{
+	int ret = 0;
+	u8 status0;
+	u8 bc_lvl;
+	enum typec_cc_polarity cc_polarity;
+	enum typec_cc_status cc_status_active, cc1, cc2;
+
+	/* set pull_up, pull_down */
+	ret = fusb302_set_cc_pull(chip, false, true);
+	if (ret < 0) {
+		fusb302_log(chip, "cannot set cc to pull down, ret=%d", ret);
+		return ret;
+	}
+	/* set polarity */
+	cc_polarity = (togdone_result == FUSB_REG_STATUS1A_TOGSS_SNK1) ?
+		      TYPEC_POLARITY_CC1 : TYPEC_POLARITY_CC2;
+	ret = fusb302_set_cc_polarity(chip, cc_polarity);
+	if (ret < 0) {
+		fusb302_log(chip, "cannot set cc polarity %s, ret=%d",
+			    cc_polarity_name[cc_polarity], ret);
+		return ret;
+	}
+	/* fusb302_set_cc_polarity() has set the correct measure block */
+	ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0);
+	if (ret < 0)
+		return ret;
+	bc_lvl = status0 & FUSB_REG_STATUS0_BC_LVL_MASK;
+	cc_status_active = fusb302_bc_lvl_to_cc(bc_lvl);
+	/* restart toggling if the cc status on the active line is OPEN */
+	if (cc_status_active == TYPEC_CC_OPEN) {
+		fusb302_log(chip, "restart toggling as CC_OPEN detected");
+		ret = fusb302_set_toggling(chip, chip->toggling_mode);
+		return ret;
+	}
+	/* update tcpm with the new cc value */
+	cc1 = (cc_polarity == TYPEC_POLARITY_CC1) ?
+	      cc_status_active : TYPEC_CC_OPEN;
+	cc2 = (cc_polarity == TYPEC_POLARITY_CC2) ?
+	      cc_status_active : TYPEC_CC_OPEN;
+	if ((chip->cc1 != cc1) || (chip->cc2 != cc2)) {
+		chip->cc1 = cc1;
+		chip->cc2 = cc2;
+		tcpm_cc_change(chip->tcpm_port);
+	}
+	/* turn off toggling */
+	ret = fusb302_set_toggling(chip, TOGGLINE_MODE_OFF);
+	if (ret < 0) {
+		fusb302_log(chip,
+			    "cannot set toggling mode off, ret=%d", ret);
+		return ret;
+	}
+	/* unmask bc_lvl interrupt */
+	ret = fusb302_i2c_clear_bits(chip, FUSB_REG_MASK, FUSB_REG_MASK_BC_LVL);
+	if (ret < 0) {
+		fusb302_log(chip,
+			    "cannot unmask bc_lcl interrupt, ret=%d", ret);
+		return ret;
+	}
+	chip->intr_bc_lvl = true;
+	fusb302_log(chip, "detected cc1=%s, cc2=%s",
+		    typec_cc_status_name[cc1],
+		    typec_cc_status_name[cc2]);
+
+	return ret;
+}
+
+static int fusb302_handle_togdone_src(struct fusb302_chip *chip,
+				      u8 togdone_result)
+{
+	/*
+	 * - set polarity (measure cc, vconn, tx)
+	 * - set pull_up, pull_down
+	 * - set cc1, cc2, and update to tcpm_port
+	 * - set I_COMP interrupt on
+	 */
+	int ret = 0;
+	u8 status0;
+	u8 ra_mda = ra_mda_value[chip->src_current_status];
+	u8 rd_mda = rd_mda_value[chip->src_current_status];
+	bool ra_comp, rd_comp;
+	enum typec_cc_polarity cc_polarity;
+	enum typec_cc_status cc_status_active, cc1, cc2;
+
+	/* set pull_up, pull_down */
+	ret = fusb302_set_cc_pull(chip, true, false);
+	if (ret < 0) {
+		fusb302_log(chip, "cannot set cc to pull up, ret=%d", ret);
+		return ret;
+	}
+	/* set polarity */
+	cc_polarity = (togdone_result == FUSB_REG_STATUS1A_TOGSS_SRC1) ?
+		      TYPEC_POLARITY_CC1 : TYPEC_POLARITY_CC2;
+	ret = fusb302_set_cc_polarity(chip, cc_polarity);
+	if (ret < 0) {
+		fusb302_log(chip, "cannot set cc polarity %s, ret=%d",
+			    cc_polarity_name[cc_polarity], ret);
+		return ret;
+	}
+	/* fusb302_set_cc_polarity() has set the correct measure block */
+	ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, rd_mda);
+	if (ret < 0)
+		return ret;
+	usleep_range(50, 100);
+	ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0);
+	if (ret < 0)
+		return ret;
+	rd_comp = !!(status0 & FUSB_REG_STATUS0_COMP);
+	if (!rd_comp) {
+		ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, ra_mda);
+		if (ret < 0)
+			return ret;
+		usleep_range(50, 100);
+		ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0);
+		if (ret < 0)
+			return ret;
+		ra_comp = !!(status0 & FUSB_REG_STATUS0_COMP);
+	}
+	if (rd_comp)
+		cc_status_active = TYPEC_CC_OPEN;
+	else if (ra_comp)
+		cc_status_active = TYPEC_CC_RD;
+	else
+		/* Ra is not supported, report as Open */
+		cc_status_active = TYPEC_CC_OPEN;
+	/* restart toggling if the cc status on the active line is OPEN */
+	if (cc_status_active == TYPEC_CC_OPEN) {
+		fusb302_log(chip, "restart toggling as CC_OPEN detected");
+		ret = fusb302_set_toggling(chip, chip->toggling_mode);
+		return ret;
+	}
+	/* update tcpm with the new cc value */
+	cc1 = (cc_polarity == TYPEC_POLARITY_CC1) ?
+	      cc_status_active : TYPEC_CC_OPEN;
+	cc2 = (cc_polarity == TYPEC_POLARITY_CC2) ?
+	      cc_status_active : TYPEC_CC_OPEN;
+	if ((chip->cc1 != cc1) || (chip->cc2 != cc2)) {
+		chip->cc1 = cc1;
+		chip->cc2 = cc2;
+		tcpm_cc_change(chip->tcpm_port);
+	}
+	/* turn off toggling */
+	ret = fusb302_set_toggling(chip, TOGGLINE_MODE_OFF);
+	if (ret < 0) {
+		fusb302_log(chip,
+			    "cannot set toggling mode off, ret=%d", ret);
+		return ret;
+	}
+	/* set MDAC to Rd threshold, and unmask I_COMP for unplug detection */
+	ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, rd_mda);
+	if (ret < 0)
+		return ret;
+	/* unmask comp_chng interrupt */
+	ret = fusb302_i2c_clear_bits(chip, FUSB_REG_MASK,
+				     FUSB_REG_MASK_COMP_CHNG);
+	if (ret < 0) {
+		fusb302_log(chip,
+			    "cannot unmask bc_lcl interrupt, ret=%d", ret);
+		return ret;
+	}
+	chip->intr_comp_chng = true;
+	fusb302_log(chip, "detected cc1=%s, cc2=%s",
+		    typec_cc_status_name[cc1],
+		    typec_cc_status_name[cc2]);
+
+	return ret;
+}
+
+static int fusb302_handle_togdone(struct fusb302_chip *chip)
+{
+	int ret = 0;
+	u8 status1a;
+	u8 togdone_result;
+
+	ret = fusb302_i2c_read(chip, FUSB_REG_STATUS1A, &status1a);
+	if (ret < 0)
+		return ret;
+	togdone_result = (status1a >> FUSB_REG_STATUS1A_TOGSS_POS) &
+			 FUSB_REG_STATUS1A_TOGSS_MASK;
+	switch (togdone_result) {
+	case FUSB_REG_STATUS1A_TOGSS_SNK1:
+	case FUSB_REG_STATUS1A_TOGSS_SNK2:
+		return fusb302_handle_togdone_snk(chip, togdone_result);
+	case FUSB_REG_STATUS1A_TOGSS_SRC1:
+	case FUSB_REG_STATUS1A_TOGSS_SRC2:
+		return fusb302_handle_togdone_src(chip, togdone_result);
+	case FUSB_REG_STATUS1A_TOGSS_AA:
+		/* doesn't support */
+		fusb302_log(chip, "AudioAccessory not supported");
+		fusb302_set_toggling(chip, chip->toggling_mode);
+		break;
+	default:
+		fusb302_log(chip, "TOGDONE with an invalid state: %d",
+			    togdone_result);
+		fusb302_set_toggling(chip, chip->toggling_mode);
+		break;
+	}
+	return ret;
+}
+
+static int fusb302_pd_reset(struct fusb302_chip *chip)
+{
+	return fusb302_i2c_set_bits(chip, FUSB_REG_RESET,
+				    FUSB_REG_RESET_PD_RESET);
+}
+
+static int fusb302_pd_read_message(struct fusb302_chip *chip,
+				   struct pd_message *msg)
+{
+	int ret = 0;
+	u8 token;
+	u8 crc[4];
+	int len;
+
+	/* first SOP token */
+	ret = fusb302_i2c_read(chip, FUSB_REG_FIFOS, &token);
+	if (ret < 0)
+		return ret;
+	ret = fusb302_i2c_block_read(chip, FUSB_REG_FIFOS, 2,
+				     (u8 *)&msg->header);
+	if (ret < 0)
+		return ret;
+	len = pd_header_cnt(msg->header) * 4;
+	/* add 4 to length to include the CRC */
+	if (len > PD_MAX_PAYLOAD * 4) {
+		fusb302_log(chip, "PD message too long %d", len);
+		return -EINVAL;
+	}
+	if (len > 0) {
+		ret = fusb302_i2c_block_read(chip, FUSB_REG_FIFOS, len,
+					     (u8 *)msg->payload);
+		if (ret < 0)
+			return ret;
+	}
+	/* another 4 bytes to read CRC out */
+	ret = fusb302_i2c_block_read(chip, FUSB_REG_FIFOS, 4, crc);
+	if (ret < 0)
+		return ret;
+	fusb302_log(chip, "PD message header: %x", msg->header);
+	fusb302_log(chip, "PD message len: %d", len);
+
+	return ret;
+}
+
+static irqreturn_t fusb302_irq_intn(int irq, void *dev_id)
+{
+	struct fusb302_chip *chip = dev_id;
+	int ret = 0;
+	u8 interrupt;
+	u8 interrupta;
+	u8 interruptb;
+	u8 status0;
+	bool vbus_present;
+	bool comp_result;
+	bool intr_togdone;
+	bool intr_bc_lvl;
+	bool intr_comp_chng;
+	struct pd_message pd_msg;
+
+	mutex_lock(&chip->lock);
+	/* grab a snapshot of intr flags */
+	intr_togdone = chip->intr_togdone;
+	intr_bc_lvl = chip->intr_bc_lvl;
+	intr_comp_chng = chip->intr_comp_chng;
+
+	ret = fusb302_i2c_read(chip, FUSB_REG_INTERRUPT, &interrupt);
+	if (ret < 0)
+		goto done;
+	ret = fusb302_i2c_read(chip, FUSB_REG_INTERRUPTA, &interrupta);
+	if (ret < 0)
+		goto done;
+	ret = fusb302_i2c_read(chip, FUSB_REG_INTERRUPTB, &interruptb);
+	if (ret < 0)
+		goto done;
+	ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0);
+	if (ret < 0)
+		goto done;
+	fusb302_log(chip,
+		    "IRQ: 0x%02x, a: 0x%02x, b: 0x%02x, status0: 0x%02x",
+		    interrupt, interrupta, interruptb, status0);
+
+	if (interrupt & FUSB_REG_INTERRUPT_VBUSOK) {
+		vbus_present = !!(status0 & FUSB_REG_STATUS0_VBUSOK);
+		fusb302_log(chip, "IRQ: VBUS_OK, vbus=%s",
+			    vbus_present ? "On" : "Off");
+		if (vbus_present != chip->vbus_present) {
+			chip->vbus_present = vbus_present;
+			tcpm_vbus_change(chip->tcpm_port);
+		}
+	}
+
+	if ((interrupta & FUSB_REG_INTERRUPTA_TOGDONE) && intr_togdone) {
+		fusb302_log(chip, "IRQ: TOGDONE");
+		ret = fusb302_handle_togdone(chip);
+		if (ret < 0) {
+			fusb302_log(chip,
+				    "handle togdone error, ret=%d", ret);
+			goto done;
+		}
+	}
+
+	if ((interrupt & FUSB_REG_INTERRUPT_BC_LVL) && intr_bc_lvl) {
+		fusb302_log(chip, "IRQ: BC_LVL, handler pending");
+		/*
+		 * as BC_LVL interrupt can be affected by PD activity,
+		 * apply delay to for the handler to wait for the PD
+		 * signaling to finish.
+		 */
+		mod_delayed_work(chip->wq, &chip->bc_lvl_handler,
+				 msecs_to_jiffies(T_BC_LVL_DEBOUNCE_DELAY_MS));
+	}
+
+	if ((interrupt & FUSB_REG_INTERRUPT_COMP_CHNG) && intr_comp_chng) {
+		comp_result = !!(status0 & FUSB_REG_STATUS0_COMP);
+		fusb302_log(chip, "IRQ: COMP_CHNG, comp=%s",
+			    comp_result ? "true" : "false");
+		if (comp_result) {
+			/* cc level > Rd_threashold, detach */
+			if (chip->cc_polarity == TYPEC_POLARITY_CC1)
+				chip->cc1 = TYPEC_CC_OPEN;
+			else
+				chip->cc2 = TYPEC_CC_OPEN;
+			tcpm_cc_change(chip->tcpm_port);
+		}
+	}
+
+	if (interrupt & FUSB_REG_INTERRUPT_COLLISION) {
+		fusb302_log(chip, "IRQ: PD collision");
+		tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_FAILED);
+	}
+
+	if (interrupta & FUSB_REG_INTERRUPTA_RETRYFAIL) {
+		fusb302_log(chip, "IRQ: PD retry failed");
+		tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_FAILED);
+	}
+
+	if (interrupta & FUSB_REG_INTERRUPTA_HARDSENT) {
+		fusb302_log(chip, "IRQ: PD hardreset sent");
+		ret = fusb302_pd_reset(chip);
+		if (ret < 0) {
+			fusb302_log(chip, "cannot PD reset, ret=%d", ret);
+			goto done;
+		}
+		tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_SUCCESS);
+	}
+
+	if (interrupta & FUSB_REG_INTERRUPTA_TX_SUCCESS) {
+		fusb302_log(chip, "IRQ: PD tx success");
+		/* read out the received good CRC */
+		ret = fusb302_pd_read_message(chip, &pd_msg);
+		if (ret < 0) {
+			fusb302_log(chip, "cannot read in GCRC, ret=%d", ret);
+			goto done;
+		}
+		tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_SUCCESS);
+	}
+
+	if (interrupta & FUSB_REG_INTERRUPTA_HARDRESET) {
+		fusb302_log(chip, "IRQ: PD received hardreset");
+		ret = fusb302_pd_reset(chip);
+		if (ret < 0) {
+			fusb302_log(chip, "cannot PD reset, ret=%d", ret);
+			goto done;
+		}
+		tcpm_pd_hard_reset(chip->tcpm_port);
+	}
+
+	if (interruptb & FUSB_REG_INTERRUPTB_GCRCSENT) {
+		fusb302_log(chip, "IRQ: PD sent good CRC");
+		ret = fusb302_pd_read_message(chip, &pd_msg);
+		if (ret < 0) {
+			fusb302_log(chip,
+				    "cannot read in PD message, ret=%d", ret);
+			goto done;
+		}
+		tcpm_pd_receive(chip->tcpm_port, &pd_msg);
+	}
+done:
+	mutex_unlock(&chip->lock);
+
+	return IRQ_HANDLED;
+}
+
+static int init_gpio(struct fusb302_chip *chip)
+{
+	struct device_node *node;
+	int ret = 0;
+
+	node = chip->dev->of_node;
+	chip->gpio_int_n = of_get_named_gpio(node, "fcs,int_n", 0);
+	if (!gpio_is_valid(chip->gpio_int_n)) {
+		ret = chip->gpio_int_n;
+		fusb302_log(chip, "cannot get named GPIO Int_N, ret=%d", ret);
+		return ret;
+	}
+	ret = devm_gpio_request(chip->dev, chip->gpio_int_n, "fcs,int_n");
+	if (ret < 0) {
+		fusb302_log(chip, "cannot request GPIO Int_N, ret=%d", ret);
+		return ret;
+	}
+	ret = gpio_direction_input(chip->gpio_int_n);
+	if (ret < 0) {
+		fusb302_log(chip,
+			    "cannot set GPIO Int_N to input, ret=%d", ret);
+		gpio_free(chip->gpio_int_n);
+		return ret;
+	}
+	ret = gpio_to_irq(chip->gpio_int_n);
+	if (ret < 0) {
+		fusb302_log(chip,
+			    "cannot request IRQ for GPIO Int_N, ret=%d", ret);
+		gpio_free(chip->gpio_int_n);
+		return ret;
+	}
+	chip->gpio_int_n_irq = ret;
+	return 0;
+}
+
+static int fusb302_probe(struct i2c_client *client,
+			 const struct i2c_device_id *id)
+{
+	struct fusb302_chip *chip;
+	struct i2c_adapter *adapter;
+	int ret = 0;
+
+	adapter = to_i2c_adapter(client->dev.parent);
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) {
+		dev_err(&client->dev,
+			"I2C/SMBus block functionality not supported!\n");
+		return -ENODEV;
+	}
+	chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->i2c_client = client;
+	i2c_set_clientdata(client, chip);
+	chip->dev = &client->dev;
+	mutex_init(&chip->lock);
+
+	ret = fusb302_debugfs_init(chip);
+	if (ret < 0)
+		return ret;
+
+	chip->wq = create_singlethread_workqueue(dev_name(chip->dev));
+	if (!chip->wq) {
+		ret = -ENOMEM;
+		goto clear_client_data;
+	}
+	INIT_DELAYED_WORK(&chip->bc_lvl_handler, fusb302_bc_lvl_handler_work);
+	init_tcpc_dev(&chip->tcpc_dev);
+
+	chip->vbus = devm_regulator_get(chip->dev, "vbus");
+	if (IS_ERR(chip->vbus)) {
+		ret = PTR_ERR(chip->vbus);
+		goto destroy_workqueue;
+	}
+
+	ret = init_gpio(chip);
+	if (ret < 0)
+		goto destroy_workqueue;
+
+	chip->tcpm_port = tcpm_register_port(&client->dev, &chip->tcpc_dev);
+	if (IS_ERR(chip->tcpm_port)) {
+		ret = PTR_ERR(chip->tcpm_port);
+		fusb302_log(chip, "cannot register tcpm port, ret=%d", ret);
+		goto destroy_workqueue;
+	}
+
+	ret = devm_request_threaded_irq(chip->dev, chip->gpio_int_n_irq,
+					NULL, fusb302_irq_intn,
+					IRQF_ONESHOT | IRQF_TRIGGER_LOW,
+					"fsc_interrupt_int_n", chip);
+	if (ret < 0) {
+		fusb302_log(chip,
+			    "cannot request IRQ for GPIO Int_N, ret=%d", ret);
+		goto tcpm_unregister_port;
+	}
+	enable_irq_wake(chip->gpio_int_n_irq);
+	return ret;
+
+tcpm_unregister_port:
+	tcpm_unregister_port(chip->tcpm_port);
+destroy_workqueue:
+	destroy_workqueue(chip->wq);
+clear_client_data:
+	i2c_set_clientdata(client, NULL);
+	fusb302_debugfs_exit(chip);
+
+	return ret;
+}
+
+static int fusb302_remove(struct i2c_client *client)
+{
+	struct fusb302_chip *chip = i2c_get_clientdata(client);
+
+	tcpm_unregister_port(chip->tcpm_port);
+	destroy_workqueue(chip->wq);
+	i2c_set_clientdata(client, NULL);
+	fusb302_debugfs_exit(chip);
+
+	return 0;
+}
+
+static int fusb302_pm_suspend(struct device *dev)
+{
+	struct fusb302_chip *chip = dev->driver_data;
+
+	if (atomic_read(&chip->i2c_busy))
+		return -EBUSY;
+	atomic_set(&chip->pm_suspend, 1);
+
+	return 0;
+}
+
+static int fusb302_pm_resume(struct device *dev)
+{
+	struct fusb302_chip *chip = dev->driver_data;
+
+	atomic_set(&chip->pm_suspend, 0);
+
+	return 0;
+}
+
+static const struct of_device_id fusb302_dt_match[] = {
+	{.compatible = "fcs,fusb302"},
+	{},
+};
+
+static const struct i2c_device_id fusb302_i2c_device_id[] = {
+	{"typec_fusb302", 0},
+	{},
+};
+
+static const struct dev_pm_ops fusb302_pm_ops = {
+	.suspend = fusb302_pm_suspend,
+	.resume = fusb302_pm_resume,
+};
+
+static struct i2c_driver fusb302_driver = {
+	.driver = {
+		   .name = "typec_fusb302",
+		   .pm = &fusb302_pm_ops,
+		   .of_match_table = of_match_ptr(fusb302_dt_match),
+		   },
+	.probe = fusb302_probe,
+	.remove = fusb302_remove,
+	.id_table = fusb302_i2c_device_id,
+};
+module_i2c_driver(fusb302_driver);
+
+MODULE_AUTHOR("Yueyao Zhu <yueyao.zhu@gmail.com>");
+MODULE_DESCRIPTION("Fairchild FUSB302 Type-C Chip Driver");
+MODULE_LICENSE("GPL");

+ 186 - 0
drivers/staging/typec/fusb302/fusb302_reg.h

@@ -0,0 +1,186 @@
+/*
+ * Copyright 2016-2017 Google, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * Fairchild FUSB302 Type-C Chip Driver
+ */
+
+#ifndef FUSB302_REG_H
+#define FUSB302_REG_H
+
+#define FUSB_REG_DEVICE_ID			0x01
+#define FUSB_REG_SWITCHES0			0x02
+#define FUSB_REG_SWITCHES0_CC2_PU_EN		BIT(7)
+#define FUSB_REG_SWITCHES0_CC1_PU_EN		BIT(6)
+#define FUSB_REG_SWITCHES0_VCONN_CC2		BIT(5)
+#define FUSB_REG_SWITCHES0_VCONN_CC1		BIT(4)
+#define FUSB_REG_SWITCHES0_MEAS_CC2		BIT(3)
+#define FUSB_REG_SWITCHES0_MEAS_CC1		BIT(2)
+#define FUSB_REG_SWITCHES0_CC2_PD_EN		BIT(1)
+#define FUSB_REG_SWITCHES0_CC1_PD_EN		BIT(0)
+#define FUSB_REG_SWITCHES1			0x03
+#define FUSB_REG_SWITCHES1_POWERROLE		BIT(7)
+#define FUSB_REG_SWITCHES1_SPECREV1		BIT(6)
+#define FUSB_REG_SWITCHES1_SPECREV0		BIT(5)
+#define FUSB_REG_SWITCHES1_DATAROLE		BIT(4)
+#define FUSB_REG_SWITCHES1_AUTO_GCRC		BIT(2)
+#define FUSB_REG_SWITCHES1_TXCC2_EN		BIT(1)
+#define FUSB_REG_SWITCHES1_TXCC1_EN		BIT(0)
+#define FUSB_REG_MEASURE			0x04
+#define FUSB_REG_MEASURE_MDAC5			BIT(7)
+#define FUSB_REG_MEASURE_MDAC4			BIT(6)
+#define FUSB_REG_MEASURE_MDAC3			BIT(5)
+#define FUSB_REG_MEASURE_MDAC2			BIT(4)
+#define FUSB_REG_MEASURE_MDAC1			BIT(3)
+#define FUSB_REG_MEASURE_MDAC0			BIT(2)
+#define FUSB_REG_MEASURE_VBUS			BIT(1)
+#define FUSB_REG_MEASURE_XXXX5			BIT(0)
+#define FUSB_REG_CONTROL0			0x06
+#define FUSB_REG_CONTROL0_TX_FLUSH		BIT(6)
+#define FUSB_REG_CONTROL0_INT_MASK		BIT(5)
+#define FUSB_REG_CONTROL0_HOST_CUR_MASK		(0xC)
+#define FUSB_REG_CONTROL0_HOST_CUR_HIGH		(0xC)
+#define FUSB_REG_CONTROL0_HOST_CUR_MED		(0x8)
+#define FUSB_REG_CONTROL0_HOST_CUR_DEF		(0x4)
+#define FUSB_REG_CONTROL0_TX_START		BIT(0)
+#define FUSB_REG_CONTROL1			0x07
+#define FUSB_REG_CONTROL1_ENSOP2DB		BIT(6)
+#define FUSB_REG_CONTROL1_ENSOP1DB		BIT(5)
+#define FUSB_REG_CONTROL1_BIST_MODE2		BIT(4)
+#define FUSB_REG_CONTROL1_RX_FLUSH		BIT(2)
+#define FUSB_REG_CONTROL1_ENSOP2		BIT(1)
+#define FUSB_REG_CONTROL1_ENSOP1		BIT(0)
+#define FUSB_REG_CONTROL2			0x08
+#define FUSB_REG_CONTROL2_MODE			BIT(1)
+#define FUSB_REG_CONTROL2_MODE_MASK		(0x6)
+#define FUSB_REG_CONTROL2_MODE_DFP		(0x6)
+#define FUSB_REG_CONTROL2_MODE_UFP		(0x4)
+#define FUSB_REG_CONTROL2_MODE_DRP		(0x2)
+#define FUSB_REG_CONTROL2_MODE_NONE		(0x0)
+#define FUSB_REG_CONTROL2_TOGGLE		BIT(0)
+#define FUSB_REG_CONTROL3			0x09
+#define FUSB_REG_CONTROL3_SEND_HARDRESET	BIT(6)
+#define FUSB_REG_CONTROL3_BIST_TMODE		BIT(5)	/* 302B Only */
+#define FUSB_REG_CONTROL3_AUTO_HARDRESET	BIT(4)
+#define FUSB_REG_CONTROL3_AUTO_SOFTRESET	BIT(3)
+#define FUSB_REG_CONTROL3_N_RETRIES		BIT(1)
+#define FUSB_REG_CONTROL3_N_RETRIES_MASK	(0x6)
+#define FUSB_REG_CONTROL3_N_RETRIES_3		(0x6)
+#define FUSB_REG_CONTROL3_N_RETRIES_2		(0x4)
+#define FUSB_REG_CONTROL3_N_RETRIES_1		(0x2)
+#define FUSB_REG_CONTROL3_AUTO_RETRY		BIT(0)
+#define FUSB_REG_MASK				0x0A
+#define FUSB_REG_MASK_VBUSOK			BIT(7)
+#define FUSB_REG_MASK_ACTIVITY			BIT(6)
+#define FUSB_REG_MASK_COMP_CHNG			BIT(5)
+#define FUSB_REG_MASK_CRC_CHK			BIT(4)
+#define FUSB_REG_MASK_ALERT			BIT(3)
+#define FUSB_REG_MASK_WAKE			BIT(2)
+#define FUSB_REG_MASK_COLLISION			BIT(1)
+#define FUSB_REG_MASK_BC_LVL			BIT(0)
+#define FUSB_REG_POWER				0x0B
+#define FUSB_REG_POWER_PWR			BIT(0)
+#define FUSB_REG_POWER_PWR_LOW			0x1
+#define FUSB_REG_POWER_PWR_MEDIUM		0x3
+#define FUSB_REG_POWER_PWR_HIGH			0x7
+#define FUSB_REG_POWER_PWR_ALL			0xF
+#define FUSB_REG_RESET				0x0C
+#define FUSB_REG_RESET_PD_RESET			BIT(1)
+#define FUSB_REG_RESET_SW_RESET			BIT(0)
+#define FUSB_REG_MASKA				0x0E
+#define FUSB_REG_MASKA_OCP_TEMP			BIT(7)
+#define FUSB_REG_MASKA_TOGDONE			BIT(6)
+#define FUSB_REG_MASKA_SOFTFAIL			BIT(5)
+#define FUSB_REG_MASKA_RETRYFAIL		BIT(4)
+#define FUSB_REG_MASKA_HARDSENT			BIT(3)
+#define FUSB_REG_MASKA_TX_SUCCESS		BIT(2)
+#define FUSB_REG_MASKA_SOFTRESET		BIT(1)
+#define FUSB_REG_MASKA_HARDRESET		BIT(0)
+#define FUSB_REG_MASKB				0x0F
+#define FUSB_REG_MASKB_GCRCSENT			BIT(0)
+#define FUSB_REG_STATUS0A			0x3C
+#define FUSB_REG_STATUS0A_SOFTFAIL		BIT(5)
+#define FUSB_REG_STATUS0A_RETRYFAIL		BIT(4)
+#define FUSB_REG_STATUS0A_POWER			BIT(2)
+#define FUSB_REG_STATUS0A_RX_SOFT_RESET		BIT(1)
+#define FUSB_REG_STATUS0A_RX_HARD_RESET		BIT(0)
+#define FUSB_REG_STATUS1A			0x3D
+#define FUSB_REG_STATUS1A_TOGSS			BIT(3)
+#define FUSB_REG_STATUS1A_TOGSS_RUNNING		0x0
+#define FUSB_REG_STATUS1A_TOGSS_SRC1		0x1
+#define FUSB_REG_STATUS1A_TOGSS_SRC2		0x2
+#define FUSB_REG_STATUS1A_TOGSS_SNK1		0x5
+#define FUSB_REG_STATUS1A_TOGSS_SNK2		0x6
+#define FUSB_REG_STATUS1A_TOGSS_AA		0x7
+#define FUSB_REG_STATUS1A_TOGSS_POS		(3)
+#define FUSB_REG_STATUS1A_TOGSS_MASK		(0x7)
+#define FUSB_REG_STATUS1A_RXSOP2DB		BIT(2)
+#define FUSB_REG_STATUS1A_RXSOP1DB		BIT(1)
+#define FUSB_REG_STATUS1A_RXSOP			BIT(0)
+#define FUSB_REG_INTERRUPTA			0x3E
+#define FUSB_REG_INTERRUPTA_OCP_TEMP		BIT(7)
+#define FUSB_REG_INTERRUPTA_TOGDONE		BIT(6)
+#define FUSB_REG_INTERRUPTA_SOFTFAIL		BIT(5)
+#define FUSB_REG_INTERRUPTA_RETRYFAIL		BIT(4)
+#define FUSB_REG_INTERRUPTA_HARDSENT		BIT(3)
+#define FUSB_REG_INTERRUPTA_TX_SUCCESS		BIT(2)
+#define FUSB_REG_INTERRUPTA_SOFTRESET		BIT(1)
+#define FUSB_REG_INTERRUPTA_HARDRESET		BIT(0)
+#define FUSB_REG_INTERRUPTB			0x3F
+#define FUSB_REG_INTERRUPTB_GCRCSENT		BIT(0)
+#define FUSB_REG_STATUS0			0x40
+#define FUSB_REG_STATUS0_VBUSOK			BIT(7)
+#define FUSB_REG_STATUS0_ACTIVITY		BIT(6)
+#define FUSB_REG_STATUS0_COMP			BIT(5)
+#define FUSB_REG_STATUS0_CRC_CHK		BIT(4)
+#define FUSB_REG_STATUS0_ALERT			BIT(3)
+#define FUSB_REG_STATUS0_WAKE			BIT(2)
+#define FUSB_REG_STATUS0_BC_LVL_MASK		0x03
+#define FUSB_REG_STATUS0_BC_LVL_0_200		0x0
+#define FUSB_REG_STATUS0_BC_LVL_200_600		0x1
+#define FUSB_REG_STATUS0_BC_LVL_600_1230	0x2
+#define FUSB_REG_STATUS0_BC_LVL_1230_MAX	0x3
+#define FUSB_REG_STATUS0_BC_LVL1		BIT(1)
+#define FUSB_REG_STATUS0_BC_LVL0		BIT(0)
+#define FUSB_REG_STATUS1			0x41
+#define FUSB_REG_STATUS1_RXSOP2			BIT(7)
+#define FUSB_REG_STATUS1_RXSOP1			BIT(6)
+#define FUSB_REG_STATUS1_RX_EMPTY		BIT(5)
+#define FUSB_REG_STATUS1_RX_FULL		BIT(4)
+#define FUSB_REG_STATUS1_TX_EMPTY		BIT(3)
+#define FUSB_REG_STATUS1_TX_FULL		BIT(2)
+#define FUSB_REG_INTERRUPT			0x42
+#define FUSB_REG_INTERRUPT_VBUSOK		BIT(7)
+#define FUSB_REG_INTERRUPT_ACTIVITY		BIT(6)
+#define FUSB_REG_INTERRUPT_COMP_CHNG		BIT(5)
+#define FUSB_REG_INTERRUPT_CRC_CHK		BIT(4)
+#define FUSB_REG_INTERRUPT_ALERT		BIT(3)
+#define FUSB_REG_INTERRUPT_WAKE			BIT(2)
+#define FUSB_REG_INTERRUPT_COLLISION		BIT(1)
+#define FUSB_REG_INTERRUPT_BC_LVL		BIT(0)
+#define FUSB_REG_FIFOS				0x43
+
+/* Tokens defined for the FUSB302 TX FIFO */
+enum fusb302_txfifo_tokens {
+	FUSB302_TKN_TXON = 0xA1,
+	FUSB302_TKN_SYNC1 = 0x12,
+	FUSB302_TKN_SYNC2 = 0x13,
+	FUSB302_TKN_SYNC3 = 0x1B,
+	FUSB302_TKN_RST1 = 0x15,
+	FUSB302_TKN_RST2 = 0x16,
+	FUSB302_TKN_PACKSYM = 0x80,
+	FUSB302_TKN_JAMCRC = 0xFF,
+	FUSB302_TKN_EOP = 0x14,
+	FUSB302_TKN_TXOFF = 0xFE,
+};
+
+#endif

+ 281 - 0
drivers/staging/typec/pd.h

@@ -0,0 +1,281 @@
+/*
+ * Copyright 2015-2017 Google, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __LINUX_USB_PD_H
+#define __LINUX_USB_PD_H
+
+#include <linux/types.h>
+#include <linux/usb/typec.h>
+
+/* USB PD Messages */
+enum pd_ctrl_msg_type {
+	/* 0 Reserved */
+	PD_CTRL_GOOD_CRC = 1,
+	PD_CTRL_GOTO_MIN = 2,
+	PD_CTRL_ACCEPT = 3,
+	PD_CTRL_REJECT = 4,
+	PD_CTRL_PING = 5,
+	PD_CTRL_PS_RDY = 6,
+	PD_CTRL_GET_SOURCE_CAP = 7,
+	PD_CTRL_GET_SINK_CAP = 8,
+	PD_CTRL_DR_SWAP = 9,
+	PD_CTRL_PR_SWAP = 10,
+	PD_CTRL_VCONN_SWAP = 11,
+	PD_CTRL_WAIT = 12,
+	PD_CTRL_SOFT_RESET = 13,
+	/* 14-15 Reserved */
+};
+
+enum pd_data_msg_type {
+	/* 0 Reserved */
+	PD_DATA_SOURCE_CAP = 1,
+	PD_DATA_REQUEST = 2,
+	PD_DATA_BIST = 3,
+	PD_DATA_SINK_CAP = 4,
+	/* 5-14 Reserved */
+	PD_DATA_VENDOR_DEF = 15,
+};
+
+#define PD_REV10	0x0
+#define PD_REV20	0x1
+
+#define PD_HEADER_CNT_SHIFT	12
+#define PD_HEADER_CNT_MASK	0x7
+#define PD_HEADER_ID_SHIFT	9
+#define PD_HEADER_ID_MASK	0x7
+#define PD_HEADER_PWR_ROLE	BIT(8)
+#define PD_HEADER_REV_SHIFT	6
+#define PD_HEADER_REV_MASK	0x3
+#define PD_HEADER_DATA_ROLE	BIT(5)
+#define PD_HEADER_TYPE_SHIFT	0
+#define PD_HEADER_TYPE_MASK	0xf
+
+#define PD_HEADER(type, pwr, data, id, cnt)				\
+	((((type) & PD_HEADER_TYPE_MASK) << PD_HEADER_TYPE_SHIFT) |	\
+	 ((pwr) == TYPEC_SOURCE ? PD_HEADER_PWR_ROLE : 0) |		\
+	 ((data) == TYPEC_HOST ? PD_HEADER_DATA_ROLE : 0) |		\
+	 (PD_REV20 << PD_HEADER_REV_SHIFT) |				\
+	 (((id) & PD_HEADER_ID_MASK) << PD_HEADER_ID_SHIFT) |		\
+	 (((cnt) & PD_HEADER_CNT_MASK) << PD_HEADER_CNT_SHIFT))
+
+#define PD_HEADER_LE(type, pwr, data, id, cnt) \
+	cpu_to_le16(PD_HEADER((type), (pwr), (data), (id), (cnt)))
+
+static inline unsigned int pd_header_cnt(u16 header)
+{
+	return (header >> PD_HEADER_CNT_SHIFT) & PD_HEADER_CNT_MASK;
+}
+
+static inline unsigned int pd_header_cnt_le(__le16 header)
+{
+	return pd_header_cnt(le16_to_cpu(header));
+}
+
+static inline unsigned int pd_header_type(u16 header)
+{
+	return (header >> PD_HEADER_TYPE_SHIFT) & PD_HEADER_TYPE_MASK;
+}
+
+static inline unsigned int pd_header_type_le(__le16 header)
+{
+	return pd_header_type(le16_to_cpu(header));
+}
+
+#define PD_MAX_PAYLOAD		7
+
+struct pd_message {
+	__le16 header;
+	__le32 payload[PD_MAX_PAYLOAD];
+} __packed;
+
+/* PDO: Power Data Object */
+#define PDO_MAX_OBJECTS		7
+
+enum pd_pdo_type {
+	PDO_TYPE_FIXED = 0,
+	PDO_TYPE_BATT = 1,
+	PDO_TYPE_VAR = 2,
+};
+
+#define PDO_TYPE_SHIFT		30
+#define PDO_TYPE_MASK		0x3
+
+#define PDO_TYPE(t)	((t) << PDO_TYPE_SHIFT)
+
+#define PDO_VOLT_MASK		0x3ff
+#define PDO_CURR_MASK		0x3ff
+#define PDO_PWR_MASK		0x3ff
+
+#define PDO_FIXED_DUAL_ROLE	BIT(29)	/* Power role swap supported */
+#define PDO_FIXED_SUSPEND	BIT(28) /* USB Suspend supported (Source) */
+#define PDO_FIXED_HIGHER_CAP	BIT(28) /* Requires more than vSafe5V (Sink) */
+#define PDO_FIXED_EXTPOWER	BIT(27) /* Externally powered */
+#define PDO_FIXED_USB_COMM	BIT(26) /* USB communications capable */
+#define PDO_FIXED_DATA_SWAP	BIT(25) /* Data role swap supported */
+#define PDO_FIXED_VOLT_SHIFT	10	/* 50mV units */
+#define PDO_FIXED_CURR_SHIFT	0	/* 10mA units */
+
+#define PDO_FIXED_VOLT(mv)	((((mv) / 50) & PDO_VOLT_MASK) << PDO_FIXED_VOLT_SHIFT)
+#define PDO_FIXED_CURR(ma)	((((ma) / 10) & PDO_CURR_MASK) << PDO_FIXED_CURR_SHIFT)
+
+#define PDO_FIXED(mv, ma, flags)			\
+	(PDO_TYPE(PDO_TYPE_FIXED) | (flags) |		\
+	 PDO_FIXED_VOLT(mv) | PDO_FIXED_CURR(ma))
+
+#define PDO_BATT_MAX_VOLT_SHIFT	20	/* 50mV units */
+#define PDO_BATT_MIN_VOLT_SHIFT	10	/* 50mV units */
+#define PDO_BATT_MAX_PWR_SHIFT	0	/* 250mW units */
+
+#define PDO_BATT_MIN_VOLT(mv) ((((mv) / 50) & PDO_VOLT_MASK) << PDO_BATT_MIN_VOLT_SHIFT)
+#define PDO_BATT_MAX_VOLT(mv) ((((mv) / 50) & PDO_VOLT_MASK) << PDO_BATT_MAX_VOLT_SHIFT)
+#define PDO_BATT_MAX_POWER(mw) ((((mw) / 250) & PDO_PWR_MASK) << PDO_BATT_MAX_PWR_SHIFT)
+
+#define PDO_BATT(min_mv, max_mv, max_mw)			\
+	(PDO_TYPE(PDO_TYPE_BATT) | PDO_BATT_MIN_VOLT(min_mv) |	\
+	 PDO_BATT_MAX_VOLT(max_mv) | PDO_BATT_MAX_POWER(max_mw))
+
+#define PDO_VAR_MAX_VOLT_SHIFT	20	/* 50mV units */
+#define PDO_VAR_MIN_VOLT_SHIFT	10	/* 50mV units */
+#define PDO_VAR_MAX_CURR_SHIFT	0	/* 10mA units */
+
+#define PDO_VAR_MIN_VOLT(mv) ((((mv) / 50) & PDO_VOLT_MASK) << PDO_VAR_MIN_VOLT_SHIFT)
+#define PDO_VAR_MAX_VOLT(mv) ((((mv) / 50) & PDO_VOLT_MASK) << PDO_VAR_MAX_VOLT_SHIFT)
+#define PDO_VAR_MAX_CURR(ma) ((((ma) / 10) & PDO_CURR_MASK) << PDO_VAR_MAX_CURR_SHIFT)
+
+#define PDO_VAR(min_mv, max_mv, max_ma)				\
+	(PDO_TYPE(PDO_TYPE_VAR) | PDO_VAR_MIN_VOLT(min_mv) |	\
+	 PDO_VAR_MAX_VOLT(max_mv) | PDO_VAR_MAX_CURR(max_ma))
+
+static inline enum pd_pdo_type pdo_type(u32 pdo)
+{
+	return (pdo >> PDO_TYPE_SHIFT) & PDO_TYPE_MASK;
+}
+
+static inline unsigned int pdo_fixed_voltage(u32 pdo)
+{
+	return ((pdo >> PDO_FIXED_VOLT_SHIFT) & PDO_VOLT_MASK) * 50;
+}
+
+static inline unsigned int pdo_min_voltage(u32 pdo)
+{
+	return ((pdo >> PDO_VAR_MIN_VOLT_SHIFT) & PDO_VOLT_MASK) * 50;
+}
+
+static inline unsigned int pdo_max_voltage(u32 pdo)
+{
+	return ((pdo >> PDO_VAR_MAX_VOLT_SHIFT) & PDO_VOLT_MASK) * 50;
+}
+
+static inline unsigned int pdo_max_current(u32 pdo)
+{
+	return ((pdo >> PDO_VAR_MAX_CURR_SHIFT) & PDO_CURR_MASK) * 10;
+}
+
+static inline unsigned int pdo_max_power(u32 pdo)
+{
+	return ((pdo >> PDO_BATT_MAX_PWR_SHIFT) & PDO_PWR_MASK) * 250;
+}
+
+/* RDO: Request Data Object */
+#define RDO_OBJ_POS_SHIFT	28
+#define RDO_OBJ_POS_MASK	0x7
+#define RDO_GIVE_BACK		BIT(27)	/* Supports reduced operating current */
+#define RDO_CAP_MISMATCH	BIT(26) /* Not satisfied by source caps */
+#define RDO_USB_COMM		BIT(25) /* USB communications capable */
+#define RDO_NO_SUSPEND		BIT(24) /* USB Suspend not supported */
+
+#define RDO_PWR_MASK			0x3ff
+#define RDO_CURR_MASK			0x3ff
+
+#define RDO_FIXED_OP_CURR_SHIFT		10
+#define RDO_FIXED_MAX_CURR_SHIFT	0
+
+#define RDO_OBJ(idx) (((idx) & RDO_OBJ_POS_MASK) << RDO_OBJ_POS_SHIFT)
+
+#define PDO_FIXED_OP_CURR(ma) ((((ma) / 10) & RDO_CURR_MASK) << RDO_FIXED_OP_CURR_SHIFT)
+#define PDO_FIXED_MAX_CURR(ma) ((((ma) / 10) & RDO_CURR_MASK) << RDO_FIXED_MAX_CURR_SHIFT)
+
+#define RDO_FIXED(idx, op_ma, max_ma, flags)			\
+	(RDO_OBJ(idx) | (flags) |				\
+	 PDO_FIXED_OP_CURR(op_ma) | PDO_FIXED_MAX_CURR(max_ma))
+
+#define RDO_BATT_OP_PWR_SHIFT		10	/* 250mW units */
+#define RDO_BATT_MAX_PWR_SHIFT		0	/* 250mW units */
+
+#define RDO_BATT_OP_PWR(mw) ((((mw) / 250) & RDO_PWR_MASK) << RDO_BATT_OP_PWR_SHIFT)
+#define RDO_BATT_MAX_PWR(mw) ((((mw) / 250) & RDO_PWR_MASK) << RDO_BATT_MAX_PWR_SHIFT)
+
+#define RDO_BATT(idx, op_mw, max_mw, flags)			\
+	(RDO_OBJ(idx) | (flags) |				\
+	 RDO_BATT_OP_PWR(op_mw) | RDO_BATT_MAX_PWR(max_mw))
+
+static inline unsigned int rdo_index(u32 rdo)
+{
+	return (rdo >> RDO_OBJ_POS_SHIFT) & RDO_OBJ_POS_MASK;
+}
+
+static inline unsigned int rdo_op_current(u32 rdo)
+{
+	return ((rdo >> RDO_FIXED_OP_CURR_SHIFT) & RDO_CURR_MASK) * 10;
+}
+
+static inline unsigned int rdo_max_current(u32 rdo)
+{
+	return ((rdo >> RDO_FIXED_MAX_CURR_SHIFT) &
+		RDO_CURR_MASK) * 10;
+}
+
+static inline unsigned int rdo_op_power(u32 rdo)
+{
+	return ((rdo >> RDO_BATT_OP_PWR_SHIFT) & RDO_PWR_MASK) * 250;
+}
+
+static inline unsigned int rdo_max_power(u32 rdo)
+{
+	return ((rdo >> RDO_BATT_MAX_PWR_SHIFT) & RDO_PWR_MASK) * 250;
+}
+
+/* USB PD timers and counters */
+#define PD_T_NO_RESPONSE	5000	/* 4.5 - 5.5 seconds */
+#define PD_T_DB_DETECT		10000	/* 10 - 15 seconds */
+#define PD_T_SEND_SOURCE_CAP	150	/* 100 - 200 ms */
+#define PD_T_SENDER_RESPONSE	60	/* 24 - 30 ms, relaxed */
+#define PD_T_SOURCE_ACTIVITY	45
+#define PD_T_SINK_ACTIVITY	135
+#define PD_T_SINK_WAIT_CAP	240
+#define PD_T_PS_TRANSITION	500
+#define PD_T_SRC_TRANSITION	35
+#define PD_T_DRP_SNK		40
+#define PD_T_DRP_SRC		30
+#define PD_T_PS_SOURCE_OFF	920
+#define PD_T_PS_SOURCE_ON	480
+#define PD_T_PS_HARD_RESET	30
+#define PD_T_SRC_RECOVER	760
+#define PD_T_SRC_RECOVER_MAX	1000
+#define PD_T_SRC_TURN_ON	275
+#define PD_T_SAFE_0V		650
+#define PD_T_VCONN_SOURCE_ON	100
+#define PD_T_SINK_REQUEST	100	/* 100 ms minimum */
+#define PD_T_ERROR_RECOVERY	100	/* minimum 25 is insufficient */
+
+#define PD_T_DRP_TRY		100	/* 75 - 150 ms */
+#define PD_T_DRP_TRYWAIT	600	/* 400 - 800 ms */
+
+#define PD_T_CC_DEBOUNCE	200	/* 100 - 200 ms */
+#define PD_T_PD_DEBOUNCE	20	/* 10 - 20 ms */
+
+#define PD_N_CAPS_COUNT		(PD_T_NO_RESPONSE / PD_T_SEND_SOURCE_CAP)
+#define PD_N_HARD_RESET_COUNT	2
+
+#endif /* __LINUX_USB_PD_H */

+ 31 - 0
drivers/staging/typec/pd_bdo.h

@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015-2017 Google, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __LINUX_USB_PD_BDO_H
+#define __LINUX_USB_PD_BDO_H
+
+/* BDO : BIST Data Object */
+#define BDO_MODE_RECV		(0 << 28)
+#define BDO_MODE_TRANSMIT	(1 << 28)
+#define BDO_MODE_COUNTERS	(2 << 28)
+#define BDO_MODE_CARRIER0	(3 << 28)
+#define BDO_MODE_CARRIER1	(4 << 28)
+#define BDO_MODE_CARRIER2	(5 << 28)
+#define BDO_MODE_CARRIER3	(6 << 28)
+#define BDO_MODE_EYE		(7 << 28)
+#define BDO_MODE_TESTDATA	(8 << 28)
+
+#define BDO_MODE_MASK(mode)	((mode) & 0xf0000000)
+
+#endif

+ 249 - 0
drivers/staging/typec/pd_vdo.h

@@ -0,0 +1,249 @@
+/*
+ * Copyright 2015-2017 Google, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __LINUX_USB_PD_VDO_H
+#define __LINUX_USB_PD_VDO_H
+
+#include "pd.h"
+
+/*
+ * VDO : Vendor Defined Message Object
+ * VDM object is minimum of VDM header + 6 additional data objects.
+ */
+
+/*
+ * VDM header
+ * ----------
+ * <31:16>  :: SVID
+ * <15>     :: VDM type ( 1b == structured, 0b == unstructured )
+ * <14:13>  :: Structured VDM version (can only be 00 == 1.0 currently)
+ * <12:11>  :: reserved
+ * <10:8>   :: object position (1-7 valid ... used for enter/exit mode only)
+ * <7:6>    :: command type (SVDM only?)
+ * <5>      :: reserved (SVDM), command type (UVDM)
+ * <4:0>    :: command
+ */
+#define VDO_MAX_SIZE 7
+#define VDO(vid, type, custom)				\
+	(((vid) << 16) |				\
+	 ((type) << 15) |				\
+	 ((custom) & 0x7FFF))
+
+#define VDO_SVDM_TYPE		(1 << 15)
+#define VDO_SVDM_VERS(x)	((x) << 13)
+#define VDO_OPOS(x)		((x) << 8)
+#define VDO_CMDT(x)		((x) << 6)
+#define VDO_OPOS_MASK		VDO_OPOS(0x7)
+#define VDO_CMDT_MASK		VDO_CMDT(0x3)
+
+#define CMDT_INIT		0
+#define CMDT_RSP_ACK		1
+#define CMDT_RSP_NAK		2
+#define CMDT_RSP_BUSY		3
+
+/* reserved for SVDM ... for Google UVDM */
+#define VDO_SRC_INITIATOR	(0 << 5)
+#define VDO_SRC_RESPONDER	(1 << 5)
+
+#define CMD_DISCOVER_IDENT	1
+#define CMD_DISCOVER_SVID	2
+#define CMD_DISCOVER_MODES	3
+#define CMD_ENTER_MODE		4
+#define CMD_EXIT_MODE		5
+#define CMD_ATTENTION		6
+
+#define VDO_CMD_VENDOR(x)    (((10 + (x)) & 0x1f))
+
+/* ChromeOS specific commands */
+#define VDO_CMD_VERSION		VDO_CMD_VENDOR(0)
+#define VDO_CMD_SEND_INFO	VDO_CMD_VENDOR(1)
+#define VDO_CMD_READ_INFO	VDO_CMD_VENDOR(2)
+#define VDO_CMD_REBOOT		VDO_CMD_VENDOR(5)
+#define VDO_CMD_FLASH_ERASE	VDO_CMD_VENDOR(6)
+#define VDO_CMD_FLASH_WRITE	VDO_CMD_VENDOR(7)
+#define VDO_CMD_ERASE_SIG	VDO_CMD_VENDOR(8)
+#define VDO_CMD_PING_ENABLE	VDO_CMD_VENDOR(10)
+#define VDO_CMD_CURRENT		VDO_CMD_VENDOR(11)
+#define VDO_CMD_FLIP		VDO_CMD_VENDOR(12)
+#define VDO_CMD_GET_LOG		VDO_CMD_VENDOR(13)
+#define VDO_CMD_CCD_EN		VDO_CMD_VENDOR(14)
+
+#define PD_VDO_VID(vdo)		((vdo) >> 16)
+#define PD_VDO_SVDM(vdo)	(((vdo) >> 15) & 1)
+#define PD_VDO_OPOS(vdo)	(((vdo) >> 8) & 0x7)
+#define PD_VDO_CMD(vdo)		((vdo) & 0x1f)
+#define PD_VDO_CMDT(vdo)	(((vdo) >> 6) & 0x3)
+
+/*
+ * SVDM Identity request -> response
+ *
+ * Request is simply properly formatted SVDM header
+ *
+ * Response is 4 data objects:
+ * [0] :: SVDM header
+ * [1] :: Identitiy header
+ * [2] :: Cert Stat VDO
+ * [3] :: (Product | Cable) VDO
+ * [4] :: AMA VDO
+ *
+ */
+#define VDO_INDEX_HDR		0
+#define VDO_INDEX_IDH		1
+#define VDO_INDEX_CSTAT		2
+#define VDO_INDEX_CABLE		3
+#define VDO_INDEX_PRODUCT	3
+#define VDO_INDEX_AMA		4
+
+/*
+ * SVDM Identity Header
+ * --------------------
+ * <31>     :: data capable as a USB host
+ * <30>     :: data capable as a USB device
+ * <29:27>  :: product type
+ * <26>     :: modal operation supported (1b == yes)
+ * <25:16>  :: Reserved, Shall be set to zero
+ * <15:0>   :: USB-IF assigned VID for this cable vendor
+ */
+#define IDH_PTYPE_UNDEF		0
+#define IDH_PTYPE_HUB		1
+#define IDH_PTYPE_PERIPH	2
+#define IDH_PTYPE_PCABLE	3
+#define IDH_PTYPE_ACABLE	4
+#define IDH_PTYPE_AMA		5
+
+#define VDO_IDH(usbh, usbd, ptype, is_modal, vid)		\
+	((usbh) << 31 | (usbd) << 30 | ((ptype) & 0x7) << 27	\
+	 | (is_modal) << 26 | ((vid) & 0xffff))
+
+#define PD_IDH_PTYPE(vdo)	(((vdo) >> 27) & 0x7)
+#define PD_IDH_VID(vdo)		((vdo) & 0xffff)
+#define PD_IDH_MODAL_SUPP(vdo)	((vdo) & (1 << 26))
+
+/*
+ * Cert Stat VDO
+ * -------------
+ * <31:0>  : USB-IF assigned XID for this cable
+ */
+#define PD_CSTAT_XID(vdo)	(vdo)
+
+/*
+ * Product VDO
+ * -----------
+ * <31:16> : USB Product ID
+ * <15:0>  : USB bcdDevice
+ */
+#define VDO_PRODUCT(pid, bcd)	(((pid) & 0xffff) << 16 | ((bcd) & 0xffff))
+#define PD_PRODUCT_PID(vdo)	(((vdo) >> 16) & 0xffff)
+
+/*
+ * Cable VDO
+ * ---------
+ * <31:28> :: Cable HW version
+ * <27:24> :: Cable FW version
+ * <23:20> :: Reserved, Shall be set to zero
+ * <19:18> :: type-C to Type-A/B/C (00b == A, 01 == B, 10 == C)
+ * <17>    :: Type-C to Plug/Receptacle (0b == plug, 1b == receptacle)
+ * <16:13> :: cable latency (0001 == <10ns(~1m length))
+ * <12:11> :: cable termination type (11b == both ends active VCONN req)
+ * <10>    :: SSTX1 Directionality support (0b == fixed, 1b == cfgable)
+ * <9>     :: SSTX2 Directionality support
+ * <8>     :: SSRX1 Directionality support
+ * <7>     :: SSRX2 Directionality support
+ * <6:5>   :: Vbus current handling capability
+ * <4>     :: Vbus through cable (0b == no, 1b == yes)
+ * <3>     :: SOP" controller present? (0b == no, 1b == yes)
+ * <2:0>   :: USB SS Signaling support
+ */
+#define CABLE_ATYPE		0
+#define CABLE_BTYPE		1
+#define CABLE_CTYPE		2
+#define CABLE_PLUG		0
+#define CABLE_RECEPTACLE	1
+#define CABLE_CURR_1A5		0
+#define CABLE_CURR_3A		1
+#define CABLE_CURR_5A		2
+#define CABLE_USBSS_U2_ONLY	0
+#define CABLE_USBSS_U31_GEN1	1
+#define CABLE_USBSS_U31_GEN2	2
+#define VDO_CABLE(hw, fw, cbl, gdr, lat, term, tx1d, tx2d, rx1d, rx2d, cur,\
+		  vps, sopp, usbss) \
+	(((hw) & 0x7) << 28 | ((fw) & 0x7) << 24 | ((cbl) & 0x3) << 18	\
+	 | (gdr) << 17 | ((lat) & 0x7) << 13 | ((term) & 0x3) << 11	\
+	 | (tx1d) << 10 | (tx2d) << 9 | (rx1d) << 8 | (rx2d) << 7	\
+	 | ((cur) & 0x3) << 5 | (vps) << 4 | (sopp) << 3		\
+	 | ((usbss) & 0x7))
+
+/*
+ * AMA VDO
+ * ---------
+ * <31:28> :: Cable HW version
+ * <27:24> :: Cable FW version
+ * <23:12> :: Reserved, Shall be set to zero
+ * <11>    :: SSTX1 Directionality support (0b == fixed, 1b == cfgable)
+ * <10>    :: SSTX2 Directionality support
+ * <9>     :: SSRX1 Directionality support
+ * <8>     :: SSRX2 Directionality support
+ * <7:5>   :: Vconn power
+ * <4>     :: Vconn power required
+ * <3>     :: Vbus power required
+ * <2:0>   :: USB SS Signaling support
+ */
+#define VDO_AMA(hw, fw, tx1d, tx2d, rx1d, rx2d, vcpwr, vcr, vbr, usbss) \
+	(((hw) & 0x7) << 28 | ((fw) & 0x7) << 24			\
+	 | (tx1d) << 11 | (tx2d) << 10 | (rx1d) << 9 | (rx2d) << 8	\
+	 | ((vcpwr) & 0x7) << 5 | (vcr) << 4 | (vbr) << 3		\
+	 | ((usbss) & 0x7))
+
+#define PD_VDO_AMA_VCONN_REQ(vdo)	(((vdo) >> 4) & 1)
+#define PD_VDO_AMA_VBUS_REQ(vdo)	(((vdo) >> 3) & 1)
+
+#define AMA_VCONN_PWR_1W	0
+#define AMA_VCONN_PWR_1W5	1
+#define AMA_VCONN_PWR_2W	2
+#define AMA_VCONN_PWR_3W	3
+#define AMA_VCONN_PWR_4W	4
+#define AMA_VCONN_PWR_5W	5
+#define AMA_VCONN_PWR_6W	6
+#define AMA_USBSS_U2_ONLY	0
+#define AMA_USBSS_U31_GEN1	1
+#define AMA_USBSS_U31_GEN2	2
+#define AMA_USBSS_BBONLY	3
+
+/*
+ * SVDM Discover SVIDs request -> response
+ *
+ * Request is properly formatted VDM Header with discover SVIDs command.
+ * Response is a set of SVIDs of all all supported SVIDs with all zero's to
+ * mark the end of SVIDs.  If more than 12 SVIDs are supported command SHOULD be
+ * repeated.
+ */
+#define VDO_SVID(svid0, svid1)	(((svid0) & 0xffff) << 16 | ((svid1) & 0xffff))
+#define PD_VDO_SVID_SVID0(vdo)	((vdo) >> 16)
+#define PD_VDO_SVID_SVID1(vdo)	((vdo) & 0xffff)
+
+/* USB-IF SIDs */
+#define USB_SID_PD		0xff00 /* power delivery */
+#define USB_SID_DISPLAYPORT	0xff01
+#define USB_SID_MHL		0xff02	/* Mobile High-Definition Link */
+
+/* VDM command timeouts (in ms) */
+
+#define PD_T_VDM_UNSTRUCTURED	500
+#define PD_T_VDM_BUSY		100
+#define PD_T_VDM_WAIT_MODE_E	100
+#define PD_T_VDM_SNDR_RSP	30
+#define PD_T_VDM_E_MODE		25
+#define PD_T_VDM_RCVR_RSP	15
+
+#endif /* __LINUX_USB_PD_VDO_H */

+ 526 - 0
drivers/staging/typec/tcpci.c

@@ -0,0 +1,526 @@
+/*
+ * Copyright 2015-2017 Google, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * USB Type-C Port Controller Interface.
+ */
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/regmap.h>
+#include <linux/usb/typec.h>
+
+#include "pd.h"
+#include "tcpci.h"
+#include "tcpm.h"
+
+#define PD_RETRY_COUNT 3
+
+struct tcpci {
+	struct device *dev;
+	struct i2c_client *client;
+
+	struct tcpm_port *port;
+
+	struct regmap *regmap;
+
+	bool controls_vbus;
+
+	struct tcpc_dev tcpc;
+};
+
+static inline struct tcpci *tcpc_to_tcpci(struct tcpc_dev *tcpc)
+{
+	return container_of(tcpc, struct tcpci, tcpc);
+}
+
+static int tcpci_read16(struct tcpci *tcpci, unsigned int reg,
+			unsigned int *val)
+{
+	return regmap_raw_read(tcpci->regmap, reg, val, sizeof(u16));
+}
+
+static int tcpci_write16(struct tcpci *tcpci, unsigned int reg, u16 val)
+{
+	return regmap_raw_write(tcpci->regmap, reg, &val, sizeof(u16));
+}
+
+static int tcpci_set_cc(struct tcpc_dev *tcpc, enum typec_cc_status cc)
+{
+	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+	unsigned int reg;
+	int ret;
+
+	switch (cc) {
+	case TYPEC_CC_RA:
+		reg = (TCPC_ROLE_CTRL_CC_RA << TCPC_ROLE_CTRL_CC1_SHIFT) |
+			(TCPC_ROLE_CTRL_CC_RA << TCPC_ROLE_CTRL_CC2_SHIFT);
+		break;
+	case TYPEC_CC_RD:
+		reg = (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC1_SHIFT) |
+			(TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC2_SHIFT);
+		break;
+	case TYPEC_CC_RP_DEF:
+		reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) |
+			(TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) |
+			(TCPC_ROLE_CTRL_RP_VAL_DEF <<
+			 TCPC_ROLE_CTRL_RP_VAL_SHIFT);
+		break;
+	case TYPEC_CC_RP_1_5:
+		reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) |
+			(TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) |
+			(TCPC_ROLE_CTRL_RP_VAL_1_5 <<
+			 TCPC_ROLE_CTRL_RP_VAL_SHIFT);
+		break;
+	case TYPEC_CC_RP_3_0:
+		reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) |
+			(TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) |
+			(TCPC_ROLE_CTRL_RP_VAL_3_0 <<
+			 TCPC_ROLE_CTRL_RP_VAL_SHIFT);
+		break;
+	case TYPEC_CC_OPEN:
+	default:
+		reg = (TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC1_SHIFT) |
+			(TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC2_SHIFT);
+		break;
+	}
+
+	ret = regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int tcpci_start_drp_toggling(struct tcpc_dev *tcpc,
+				    enum typec_cc_status cc)
+{
+	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+	unsigned int reg = TCPC_ROLE_CTRL_DRP;
+
+	switch (cc) {
+	default:
+	case TYPEC_CC_RP_DEF:
+		reg |= (TCPC_ROLE_CTRL_RP_VAL_DEF <<
+			TCPC_ROLE_CTRL_RP_VAL_SHIFT);
+		break;
+	case TYPEC_CC_RP_1_5:
+		reg |= (TCPC_ROLE_CTRL_RP_VAL_1_5 <<
+			TCPC_ROLE_CTRL_RP_VAL_SHIFT);
+		break;
+	case TYPEC_CC_RP_3_0:
+		reg |= (TCPC_ROLE_CTRL_RP_VAL_3_0 <<
+			TCPC_ROLE_CTRL_RP_VAL_SHIFT);
+		break;
+	}
+
+	return regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg);
+}
+
+static enum typec_cc_status tcpci_to_typec_cc(unsigned int cc, bool sink)
+{
+	switch (cc) {
+	case 0x1:
+		return sink ? TYPEC_CC_RP_DEF : TYPEC_CC_RA;
+	case 0x2:
+		return sink ? TYPEC_CC_RP_1_5 : TYPEC_CC_RD;
+	case 0x3:
+		if (sink)
+			return TYPEC_CC_RP_3_0;
+	case 0x0:
+	default:
+		return TYPEC_CC_OPEN;
+	}
+}
+
+static int tcpci_get_cc(struct tcpc_dev *tcpc,
+			enum typec_cc_status *cc1, enum typec_cc_status *cc2)
+{
+	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+	unsigned int reg;
+	int ret;
+
+	ret = regmap_read(tcpci->regmap, TCPC_CC_STATUS, &reg);
+	if (ret < 0)
+		return ret;
+
+	*cc1 = tcpci_to_typec_cc((reg >> TCPC_CC_STATUS_CC1_SHIFT) &
+				 TCPC_CC_STATUS_CC1_MASK,
+				 reg & TCPC_CC_STATUS_TERM);
+	*cc2 = tcpci_to_typec_cc((reg >> TCPC_CC_STATUS_CC2_SHIFT) &
+				 TCPC_CC_STATUS_CC2_MASK,
+				 reg & TCPC_CC_STATUS_TERM);
+
+	return 0;
+}
+
+static int tcpci_set_polarity(struct tcpc_dev *tcpc,
+			      enum typec_cc_polarity polarity)
+{
+	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+	int ret;
+
+	ret = regmap_write(tcpci->regmap, TCPC_TCPC_CTRL,
+			   (polarity == TYPEC_POLARITY_CC2) ?
+			   TCPC_TCPC_CTRL_ORIENTATION : 0);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int tcpci_set_vconn(struct tcpc_dev *tcpc, bool enable)
+{
+	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+	int ret;
+
+	ret = regmap_write(tcpci->regmap, TCPC_POWER_CTRL,
+			   enable ? TCPC_POWER_CTRL_VCONN_ENABLE : 0);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int tcpci_set_roles(struct tcpc_dev *tcpc, bool attached,
+			   enum typec_role role, enum typec_data_role data)
+{
+	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+	unsigned int reg;
+	int ret;
+
+	reg = PD_REV20 << TCPC_MSG_HDR_INFO_REV_SHIFT;
+	if (role == TYPEC_SOURCE)
+		reg |= TCPC_MSG_HDR_INFO_PWR_ROLE;
+	if (data == TYPEC_HOST)
+		reg |= TCPC_MSG_HDR_INFO_DATA_ROLE;
+	ret = regmap_write(tcpci->regmap, TCPC_MSG_HDR_INFO, reg);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int tcpci_set_pd_rx(struct tcpc_dev *tcpc, bool enable)
+{
+	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+	unsigned int reg = 0;
+	int ret;
+
+	if (enable)
+		reg = TCPC_RX_DETECT_SOP | TCPC_RX_DETECT_HARD_RESET;
+	ret = regmap_write(tcpci->regmap, TCPC_RX_DETECT, reg);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int tcpci_get_vbus(struct tcpc_dev *tcpc)
+{
+	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+	unsigned int reg;
+	int ret;
+
+	ret = regmap_read(tcpci->regmap, TCPC_POWER_STATUS, &reg);
+	if (ret < 0)
+		return ret;
+
+	return !!(reg & TCPC_POWER_STATUS_VBUS_PRES);
+}
+
+static int tcpci_set_vbus(struct tcpc_dev *tcpc, bool source, bool sink)
+{
+	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+	int ret;
+
+	/* Disable both source and sink first before enabling anything */
+
+	if (!source) {
+		ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
+				   TCPC_CMD_DISABLE_SRC_VBUS);
+		if (ret < 0)
+			return ret;
+	}
+
+	if (!sink) {
+		ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
+				   TCPC_CMD_DISABLE_SINK_VBUS);
+		if (ret < 0)
+			return ret;
+	}
+
+	if (source) {
+		ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
+				   TCPC_CMD_SRC_VBUS_DEFAULT);
+		if (ret < 0)
+			return ret;
+	}
+
+	if (sink) {
+		ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
+				   TCPC_CMD_SINK_VBUS);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int tcpci_pd_transmit(struct tcpc_dev *tcpc,
+			     enum tcpm_transmit_type type,
+			     const struct pd_message *msg)
+{
+	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+	unsigned int reg, cnt, header;
+	int ret;
+
+	cnt = msg ? pd_header_cnt(msg->header) * 4 : 0;
+	ret = regmap_write(tcpci->regmap, TCPC_TX_BYTE_CNT, cnt + 2);
+	if (ret < 0)
+		return ret;
+
+	header = msg ? msg->header : 0;
+	ret = tcpci_write16(tcpci, TCPC_TX_HDR, header);
+	if (ret < 0)
+		return ret;
+
+	if (cnt > 0) {
+		ret = regmap_raw_write(tcpci->regmap, TCPC_TX_DATA,
+				       &msg->payload, cnt);
+		if (ret < 0)
+			return ret;
+	}
+
+	reg = (PD_RETRY_COUNT << TCPC_TRANSMIT_RETRY_SHIFT) |
+		(type << TCPC_TRANSMIT_TYPE_SHIFT);
+	ret = regmap_write(tcpci->regmap, TCPC_TRANSMIT, reg);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int tcpci_init(struct tcpc_dev *tcpc)
+{
+	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+	unsigned long timeout = jiffies + msecs_to_jiffies(2000); /* XXX */
+	unsigned int reg;
+	int ret;
+
+	while (time_before_eq(jiffies, timeout)) {
+		ret = regmap_read(tcpci->regmap, TCPC_POWER_STATUS, &reg);
+		if (ret < 0)
+			return ret;
+		if (!(reg & TCPC_POWER_STATUS_UNINIT))
+			break;
+		usleep_range(10000, 20000);
+	}
+	if (time_after(jiffies, timeout))
+		return -ETIMEDOUT;
+
+	/* Clear all events */
+	ret = tcpci_write16(tcpci, TCPC_ALERT, 0xffff);
+	if (ret < 0)
+		return ret;
+
+	if (tcpci->controls_vbus)
+		reg = TCPC_POWER_STATUS_VBUS_PRES;
+	else
+		reg = 0;
+	ret = regmap_write(tcpci->regmap, TCPC_POWER_STATUS_MASK, reg);
+	if (ret < 0)
+		return ret;
+
+	reg = TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_FAILED |
+		TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_RX_STATUS |
+		TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_CC_STATUS;
+	if (tcpci->controls_vbus)
+		reg |= TCPC_ALERT_POWER_STATUS;
+	return tcpci_write16(tcpci, TCPC_ALERT_MASK, reg);
+}
+
+static irqreturn_t tcpci_irq(int irq, void *dev_id)
+{
+	struct tcpci *tcpci = dev_id;
+	unsigned int status, reg;
+
+	tcpci_read16(tcpci, TCPC_ALERT, &status);
+
+	/*
+	 * Clear alert status for everything except RX_STATUS, which shouldn't
+	 * be cleared until we have successfully retrieved message.
+	 */
+	if (status & ~TCPC_ALERT_RX_STATUS)
+		tcpci_write16(tcpci, TCPC_ALERT,
+			      status & ~TCPC_ALERT_RX_STATUS);
+
+	if (status & TCPC_ALERT_CC_STATUS)
+		tcpm_cc_change(tcpci->port);
+
+	if (status & TCPC_ALERT_POWER_STATUS) {
+		regmap_read(tcpci->regmap, TCPC_POWER_STATUS_MASK, &reg);
+
+		/*
+		 * If power status mask has been reset, then the TCPC
+		 * has reset.
+		 */
+		if (reg == 0xff)
+			tcpm_tcpc_reset(tcpci->port);
+		else
+			tcpm_vbus_change(tcpci->port);
+	}
+
+	if (status & TCPC_ALERT_RX_STATUS) {
+		struct pd_message msg;
+		unsigned int cnt;
+
+		regmap_read(tcpci->regmap, TCPC_RX_BYTE_CNT, &cnt);
+
+		tcpci_read16(tcpci, TCPC_RX_HDR, &reg);
+		msg.header = reg;
+
+		if (WARN_ON(cnt > sizeof(msg.payload)))
+			cnt = sizeof(msg.payload);
+
+		if (cnt > 0)
+			regmap_raw_read(tcpci->regmap, TCPC_RX_DATA,
+					&msg.payload, cnt);
+
+		/* Read complete, clear RX status alert bit */
+		tcpci_write16(tcpci, TCPC_ALERT, TCPC_ALERT_RX_STATUS);
+
+		tcpm_pd_receive(tcpci->port, &msg);
+	}
+
+	if (status & TCPC_ALERT_RX_HARD_RST)
+		tcpm_pd_hard_reset(tcpci->port);
+
+	if (status & TCPC_ALERT_TX_SUCCESS)
+		tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_SUCCESS);
+	else if (status & TCPC_ALERT_TX_DISCARDED)
+		tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_DISCARDED);
+	else if (status & TCPC_ALERT_TX_FAILED)
+		tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_FAILED);
+
+	return IRQ_HANDLED;
+}
+
+static const struct regmap_config tcpci_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+
+	.max_register = 0x7F, /* 0x80 .. 0xFF are vendor defined */
+};
+
+const struct tcpc_config tcpci_tcpc_config = {
+	.type = TYPEC_PORT_DFP,
+	.default_role = TYPEC_SINK,
+};
+
+static int tcpci_parse_config(struct tcpci *tcpci)
+{
+	tcpci->controls_vbus = true; /* XXX */
+
+	/* TODO: Populate struct tcpc_config from ACPI/device-tree */
+	tcpci->tcpc.config = &tcpci_tcpc_config;
+
+	return 0;
+}
+
+static int tcpci_probe(struct i2c_client *client,
+		       const struct i2c_device_id *i2c_id)
+{
+	struct tcpci *tcpci;
+	int err;
+
+	tcpci = devm_kzalloc(&client->dev, sizeof(*tcpci), GFP_KERNEL);
+	if (!tcpci)
+		return -ENOMEM;
+
+	tcpci->client = client;
+	tcpci->dev = &client->dev;
+	i2c_set_clientdata(client, tcpci);
+	tcpci->regmap = devm_regmap_init_i2c(client, &tcpci_regmap_config);
+	if (IS_ERR(tcpci->regmap))
+		return PTR_ERR(tcpci->regmap);
+
+	tcpci->tcpc.init = tcpci_init;
+	tcpci->tcpc.get_vbus = tcpci_get_vbus;
+	tcpci->tcpc.set_vbus = tcpci_set_vbus;
+	tcpci->tcpc.set_cc = tcpci_set_cc;
+	tcpci->tcpc.get_cc = tcpci_get_cc;
+	tcpci->tcpc.set_polarity = tcpci_set_polarity;
+	tcpci->tcpc.set_vconn = tcpci_set_vconn;
+	tcpci->tcpc.start_drp_toggling = tcpci_start_drp_toggling;
+
+	tcpci->tcpc.set_pd_rx = tcpci_set_pd_rx;
+	tcpci->tcpc.set_roles = tcpci_set_roles;
+	tcpci->tcpc.pd_transmit = tcpci_pd_transmit;
+
+	err = tcpci_parse_config(tcpci);
+	if (err < 0)
+		return err;
+
+	/* Disable chip interrupts */
+	tcpci_write16(tcpci, TCPC_ALERT_MASK, 0);
+
+	err = devm_request_threaded_irq(tcpci->dev, client->irq, NULL,
+					tcpci_irq,
+					IRQF_ONESHOT | IRQF_TRIGGER_LOW,
+					dev_name(tcpci->dev), tcpci);
+	if (err < 0)
+		return err;
+
+	tcpci->port = tcpm_register_port(tcpci->dev, &tcpci->tcpc);
+	return PTR_ERR_OR_ZERO(tcpci->port);
+}
+
+static int tcpci_remove(struct i2c_client *client)
+{
+	struct tcpci *tcpci = i2c_get_clientdata(client);
+
+	tcpm_unregister_port(tcpci->port);
+
+	return 0;
+}
+
+static const struct i2c_device_id tcpci_id[] = {
+	{ "tcpci", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, tcpci_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id tcpci_of_match[] = {
+	{ .compatible = "usb,tcpci", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, tcpci_of_match);
+#endif
+
+static struct i2c_driver tcpci_i2c_driver = {
+	.driver = {
+		.name = "tcpci",
+		.of_match_table = of_match_ptr(tcpci_of_match),
+	},
+	.probe = tcpci_probe,
+	.remove = tcpci_remove,
+	.id_table = tcpci_id,
+};
+module_i2c_driver(tcpci_i2c_driver);
+
+MODULE_DESCRIPTION("USB Type-C Port Controller Interface driver");
+MODULE_LICENSE("GPL");

+ 133 - 0
drivers/staging/typec/tcpci.h

@@ -0,0 +1,133 @@
+/*
+ * Copyright 2015-2017 Google, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * USB Type-C Port Controller Interface.
+ */
+
+#ifndef __LINUX_USB_TCPCI_H
+#define __LINUX_USB_TCPCI_H
+
+#define TCPC_VENDOR_ID			0x0
+#define TCPC_PRODUCT_ID			0x2
+#define TCPC_BCD_DEV			0x4
+#define TCPC_TC_REV			0x6
+#define TCPC_PD_REV			0x8
+#define TCPC_PD_INT_REV			0xa
+
+#define TCPC_ALERT			0x10
+#define TCPC_ALERT_VBUS_DISCNCT		BIT(11)
+#define TCPC_ALERT_RX_BUF_OVF		BIT(10)
+#define TCPC_ALERT_FAULT		BIT(9)
+#define TCPC_ALERT_V_ALARM_LO		BIT(8)
+#define TCPC_ALERT_V_ALARM_HI		BIT(7)
+#define TCPC_ALERT_TX_SUCCESS		BIT(6)
+#define TCPC_ALERT_TX_DISCARDED		BIT(5)
+#define TCPC_ALERT_TX_FAILED		BIT(4)
+#define TCPC_ALERT_RX_HARD_RST		BIT(3)
+#define TCPC_ALERT_RX_STATUS		BIT(2)
+#define TCPC_ALERT_POWER_STATUS		BIT(1)
+#define TCPC_ALERT_CC_STATUS		BIT(0)
+
+#define TCPC_ALERT_MASK			0x12
+#define TCPC_POWER_STATUS_MASK		0x14
+#define TCPC_FAULT_STATUS_MASK		0x15
+#define TCPC_CONFIG_STD_OUTPUT		0x18
+
+#define TCPC_TCPC_CTRL			0x19
+#define TCPC_TCPC_CTRL_ORIENTATION	BIT(0)
+
+#define TCPC_ROLE_CTRL			0x1a
+#define TCPC_ROLE_CTRL_DRP		BIT(6)
+#define TCPC_ROLE_CTRL_RP_VAL_SHIFT	4
+#define TCPC_ROLE_CTRL_RP_VAL_MASK	0x3
+#define TCPC_ROLE_CTRL_RP_VAL_DEF	0x0
+#define TCPC_ROLE_CTRL_RP_VAL_1_5	0x1
+#define TCPC_ROLE_CTRL_RP_VAL_3_0	0x2
+#define TCPC_ROLE_CTRL_CC2_SHIFT	2
+#define TCPC_ROLE_CTRL_CC2_MASK		0x3
+#define TCPC_ROLE_CTRL_CC1_SHIFT	0
+#define TCPC_ROLE_CTRL_CC1_MASK		0x3
+#define TCPC_ROLE_CTRL_CC_RA		0x0
+#define TCPC_ROLE_CTRL_CC_RP		0x1
+#define TCPC_ROLE_CTRL_CC_RD		0x2
+#define TCPC_ROLE_CTRL_CC_OPEN		0x3
+
+#define TCPC_FAULT_CTRL			0x1b
+
+#define TCPC_POWER_CTRL			0x1c
+#define TCPC_POWER_CTRL_VCONN_ENABLE	BIT(0)
+
+#define TCPC_CC_STATUS			0x1d
+#define TCPC_CC_STATUS_TERM		BIT(4)
+#define TCPC_CC_STATUS_CC2_SHIFT	2
+#define TCPC_CC_STATUS_CC2_MASK		0x3
+#define TCPC_CC_STATUS_CC1_SHIFT	0
+#define TCPC_CC_STATUS_CC1_MASK		0x3
+
+#define TCPC_POWER_STATUS		0x1e
+#define TCPC_POWER_STATUS_UNINIT	BIT(6)
+#define TCPC_POWER_STATUS_VBUS_DET	BIT(3)
+#define TCPC_POWER_STATUS_VBUS_PRES	BIT(2)
+
+#define TCPC_FAULT_STATUS		0x1f
+
+#define TCPC_COMMAND			0x23
+#define TCPC_CMD_WAKE_I2C		0x11
+#define TCPC_CMD_DISABLE_VBUS_DETECT	0x22
+#define TCPC_CMD_ENABLE_VBUS_DETECT	0x33
+#define TCPC_CMD_DISABLE_SINK_VBUS	0x44
+#define TCPC_CMD_SINK_VBUS		0x55
+#define TCPC_CMD_DISABLE_SRC_VBUS	0x66
+#define TCPC_CMD_SRC_VBUS_DEFAULT	0x77
+#define TCPC_CMD_SRC_VBUS_HIGH		0x88
+#define TCPC_CMD_LOOK4CONNECTION	0x99
+#define TCPC_CMD_RXONEMORE		0xAA
+#define TCPC_CMD_I2C_IDLE		0xFF
+
+#define TCPC_DEV_CAP_1			0x24
+#define TCPC_DEV_CAP_2			0x26
+#define TCPC_STD_INPUT_CAP		0x28
+#define TCPC_STD_OUTPUT_CAP		0x29
+
+#define TCPC_MSG_HDR_INFO		0x2e
+#define TCPC_MSG_HDR_INFO_DATA_ROLE	BIT(3)
+#define TCPC_MSG_HDR_INFO_PWR_ROLE	BIT(0)
+#define TCPC_MSG_HDR_INFO_REV_SHIFT	1
+#define TCPC_MSG_HDR_INFO_REV_MASK	0x3
+
+#define TCPC_RX_DETECT			0x2f
+#define TCPC_RX_DETECT_HARD_RESET	BIT(5)
+#define TCPC_RX_DETECT_SOP		BIT(0)
+
+#define TCPC_RX_BYTE_CNT		0x30
+#define TCPC_RX_BUF_FRAME_TYPE		0x31
+#define TCPC_RX_HDR			0x32
+#define TCPC_RX_DATA			0x34 /* through 0x4f */
+
+#define TCPC_TRANSMIT			0x50
+#define TCPC_TRANSMIT_RETRY_SHIFT	4
+#define TCPC_TRANSMIT_RETRY_MASK	0x3
+#define TCPC_TRANSMIT_TYPE_SHIFT	0
+#define TCPC_TRANSMIT_TYPE_MASK		0x7
+
+#define TCPC_TX_BYTE_CNT		0x51
+#define TCPC_TX_HDR			0x52
+#define TCPC_TX_DATA			0x54 /* through 0x6f */
+
+#define TCPC_VBUS_VOLTAGE			0x70
+#define TCPC_VBUS_SINK_DISCONNECT_THRESH	0x72
+#define TCPC_VBUS_STOP_DISCHARGE_THRESH		0x74
+#define TCPC_VBUS_VOLTAGE_ALARM_HI_CFG		0x76
+#define TCPC_VBUS_VOLTAGE_ALARM_LO_CFG		0x78
+
+#endif /* __LINUX_USB_TCPCI_H */

+ 3465 - 0
drivers/staging/typec/tcpm.c

@@ -0,0 +1,3465 @@
+/*
+ * Copyright 2015-2017 Google, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * USB Power Delivery protocol stack.
+ */
+
+#include <linux/completion.h>
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/proc_fs.h>
+#include <linux/sched/clock.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/usb/typec.h>
+#include <linux/workqueue.h>
+
+#include "pd.h"
+#include "pd_vdo.h"
+#include "pd_bdo.h"
+#include "tcpm.h"
+
+#define FOREACH_STATE(S)			\
+	S(INVALID_STATE),			\
+	S(DRP_TOGGLING),			\
+	S(SRC_UNATTACHED),			\
+	S(SRC_ATTACH_WAIT),			\
+	S(SRC_ATTACHED),			\
+	S(SRC_STARTUP),				\
+	S(SRC_SEND_CAPABILITIES),		\
+	S(SRC_NEGOTIATE_CAPABILITIES),		\
+	S(SRC_TRANSITION_SUPPLY),		\
+	S(SRC_READY),				\
+	S(SRC_WAIT_NEW_CAPABILITIES),		\
+						\
+	S(SNK_UNATTACHED),			\
+	S(SNK_ATTACH_WAIT),			\
+	S(SNK_DEBOUNCED),			\
+	S(SNK_ATTACHED),			\
+	S(SNK_STARTUP),				\
+	S(SNK_DISCOVERY),			\
+	S(SNK_DISCOVERY_DEBOUNCE),		\
+	S(SNK_DISCOVERY_DEBOUNCE_DONE),		\
+	S(SNK_WAIT_CAPABILITIES),		\
+	S(SNK_NEGOTIATE_CAPABILITIES),		\
+	S(SNK_TRANSITION_SINK),			\
+	S(SNK_TRANSITION_SINK_VBUS),		\
+	S(SNK_READY),				\
+						\
+	S(ACC_UNATTACHED),			\
+	S(DEBUG_ACC_ATTACHED),			\
+	S(AUDIO_ACC_ATTACHED),			\
+	S(AUDIO_ACC_DEBOUNCE),			\
+						\
+	S(HARD_RESET_SEND),			\
+	S(HARD_RESET_START),			\
+	S(SRC_HARD_RESET_VBUS_OFF),		\
+	S(SRC_HARD_RESET_VBUS_ON),		\
+	S(SNK_HARD_RESET_SINK_OFF),		\
+	S(SNK_HARD_RESET_WAIT_VBUS),		\
+	S(SNK_HARD_RESET_SINK_ON),		\
+						\
+	S(SOFT_RESET),				\
+	S(SOFT_RESET_SEND),			\
+						\
+	S(DR_SWAP_ACCEPT),			\
+	S(DR_SWAP_SEND),			\
+	S(DR_SWAP_SEND_TIMEOUT),		\
+	S(DR_SWAP_CANCEL),			\
+	S(DR_SWAP_CHANGE_DR),			\
+						\
+	S(PR_SWAP_ACCEPT),			\
+	S(PR_SWAP_SEND),			\
+	S(PR_SWAP_SEND_TIMEOUT),		\
+	S(PR_SWAP_CANCEL),			\
+	S(PR_SWAP_START),			\
+	S(PR_SWAP_SRC_SNK_TRANSITION_OFF),	\
+	S(PR_SWAP_SRC_SNK_SOURCE_OFF),		\
+	S(PR_SWAP_SRC_SNK_SINK_ON),		\
+	S(PR_SWAP_SNK_SRC_SINK_OFF),		\
+	S(PR_SWAP_SNK_SRC_SOURCE_ON),		\
+						\
+	S(VCONN_SWAP_ACCEPT),			\
+	S(VCONN_SWAP_SEND),			\
+	S(VCONN_SWAP_SEND_TIMEOUT),		\
+	S(VCONN_SWAP_CANCEL),			\
+	S(VCONN_SWAP_START),			\
+	S(VCONN_SWAP_WAIT_FOR_VCONN),		\
+	S(VCONN_SWAP_TURN_ON_VCONN),		\
+	S(VCONN_SWAP_TURN_OFF_VCONN),		\
+						\
+	S(SNK_TRY),				\
+	S(SNK_TRY_WAIT),			\
+	S(SRC_TRYWAIT),				\
+	S(SRC_TRYWAIT_UNATTACHED),		\
+						\
+	S(SRC_TRY),				\
+	S(SRC_TRY_DEBOUNCE),			\
+	S(SNK_TRYWAIT),				\
+	S(SNK_TRYWAIT_DEBOUNCE),		\
+	S(SNK_TRYWAIT_VBUS),			\
+	S(BIST_RX),				\
+						\
+	S(ERROR_RECOVERY),			\
+	S(ERROR_RECOVERY_WAIT_OFF)
+
+#define GENERATE_ENUM(e)	e
+#define GENERATE_STRING(s)	#s
+
+enum tcpm_state {
+	FOREACH_STATE(GENERATE_ENUM)
+};
+
+static const char * const tcpm_states[] = {
+	FOREACH_STATE(GENERATE_STRING)
+};
+
+enum vdm_states {
+	VDM_STATE_ERR_BUSY = -3,
+	VDM_STATE_ERR_SEND = -2,
+	VDM_STATE_ERR_TMOUT = -1,
+	VDM_STATE_DONE = 0,
+	/* Anything >0 represents an active state */
+	VDM_STATE_READY = 1,
+	VDM_STATE_BUSY = 2,
+	VDM_STATE_WAIT_RSP_BUSY = 3,
+};
+
+enum pd_msg_request {
+	PD_MSG_NONE = 0,
+	PD_MSG_CTRL_REJECT,
+	PD_MSG_CTRL_WAIT,
+	PD_MSG_DATA_SINK_CAP,
+	PD_MSG_DATA_SOURCE_CAP,
+};
+
+/* Events from low level driver */
+
+#define TCPM_CC_EVENT		BIT(0)
+#define TCPM_VBUS_EVENT		BIT(1)
+#define TCPM_RESET_EVENT	BIT(2)
+
+#define LOG_BUFFER_ENTRIES	1024
+#define LOG_BUFFER_ENTRY_SIZE	128
+
+/* Alternate mode support */
+
+#define SVID_DISCOVERY_MAX	16
+
+struct pd_mode_data {
+	int svid_index;		/* current SVID index		*/
+	int nsvids;
+	u16 svids[SVID_DISCOVERY_MAX];
+	int altmodes;		/* number of alternate modes	*/
+	struct typec_altmode_desc altmode_desc[SVID_DISCOVERY_MAX];
+};
+
+struct tcpm_port {
+	struct device *dev;
+
+	struct mutex lock;		/* tcpm state machine lock */
+	struct workqueue_struct *wq;
+
+	struct typec_capability typec_caps;
+	struct typec_port *typec_port;
+
+	struct tcpc_dev	*tcpc;
+
+	enum typec_role vconn_role;
+	enum typec_role pwr_role;
+	enum typec_data_role data_role;
+	enum typec_pwr_opmode pwr_opmode;
+
+	struct usb_pd_identity partner_ident;
+	struct typec_partner_desc partner_desc;
+	struct typec_partner *partner;
+
+	enum typec_cc_status cc_req;
+
+	enum typec_cc_status cc1;
+	enum typec_cc_status cc2;
+	enum typec_cc_polarity polarity;
+
+	bool attached;
+	bool connected;
+	bool vbus_present;
+	bool vbus_never_low;
+	bool vbus_source;
+	bool vbus_charge;
+
+	bool send_discover;
+	bool op_vsafe5v;
+
+	int try_role;
+	int try_snk_count;
+	int try_src_count;
+
+	enum pd_msg_request queued_message;
+
+	enum tcpm_state enter_state;
+	enum tcpm_state prev_state;
+	enum tcpm_state state;
+	enum tcpm_state delayed_state;
+	unsigned long delayed_runtime;
+	unsigned long delay_ms;
+
+	spinlock_t pd_event_lock;
+	u32 pd_events;
+
+	struct work_struct event_work;
+	struct delayed_work state_machine;
+	struct delayed_work vdm_state_machine;
+	bool state_machine_running;
+
+	struct completion tx_complete;
+	enum tcpm_transmit_status tx_status;
+
+	struct mutex swap_lock;		/* swap command lock */
+	bool swap_pending;
+	struct completion swap_complete;
+	int swap_status;
+
+	unsigned int message_id;
+	unsigned int caps_count;
+	unsigned int hard_reset_count;
+	bool pd_capable;
+	bool explicit_contract;
+
+	/* Partner capabilities/requests */
+	u32 sink_request;
+	u32 source_caps[PDO_MAX_OBJECTS];
+	unsigned int nr_source_caps;
+	u32 sink_caps[PDO_MAX_OBJECTS];
+	unsigned int nr_sink_caps;
+
+	/* Local capabilities */
+	u32 src_pdo[PDO_MAX_OBJECTS];
+	unsigned int nr_src_pdo;
+	u32 snk_pdo[PDO_MAX_OBJECTS];
+	unsigned int nr_snk_pdo;
+
+	unsigned int max_snk_mv;
+	unsigned int max_snk_ma;
+	unsigned int max_snk_mw;
+	unsigned int operating_snk_mw;
+
+	/* Requested current / voltage */
+	u32 current_limit;
+	u32 supply_voltage;
+
+	u32 bist_request;
+
+	/* PD state for Vendor Defined Messages */
+	enum vdm_states vdm_state;
+	u32 vdm_retries;
+	/* next Vendor Defined Message to send */
+	u32 vdo_data[VDO_MAX_SIZE];
+	u8 vdo_count;
+	/* VDO to retry if UFP responder replied busy */
+	u32 vdo_retry;
+
+	/* Alternate mode data */
+
+	struct pd_mode_data mode_data;
+	struct typec_altmode *partner_altmode[SVID_DISCOVERY_MAX];
+	struct typec_altmode *port_altmode[SVID_DISCOVERY_MAX];
+
+#ifdef CONFIG_DEBUG_FS
+	struct dentry *dentry;
+	struct mutex logbuffer_lock;	/* log buffer access lock */
+	int logbuffer_head;
+	int logbuffer_tail;
+	u8 *logbuffer[LOG_BUFFER_ENTRIES];
+#endif
+};
+
+struct pd_rx_event {
+	struct work_struct work;
+	struct tcpm_port *port;
+	struct pd_message msg;
+};
+
+#define tcpm_cc_is_sink(cc) \
+	((cc) == TYPEC_CC_RP_DEF || (cc) == TYPEC_CC_RP_1_5 || \
+	 (cc) == TYPEC_CC_RP_3_0)
+
+#define tcpm_port_is_sink(port) \
+	((tcpm_cc_is_sink((port)->cc1) && !tcpm_cc_is_sink((port)->cc2)) || \
+	 (tcpm_cc_is_sink((port)->cc2) && !tcpm_cc_is_sink((port)->cc1)))
+
+#define tcpm_cc_is_source(cc) ((cc) == TYPEC_CC_RD)
+#define tcpm_cc_is_audio(cc) ((cc) == TYPEC_CC_RA)
+#define tcpm_cc_is_open(cc) ((cc) == TYPEC_CC_OPEN)
+
+#define tcpm_port_is_source(port) \
+	((tcpm_cc_is_source((port)->cc1) && \
+	 !tcpm_cc_is_source((port)->cc2)) || \
+	 (tcpm_cc_is_source((port)->cc2) && \
+	  !tcpm_cc_is_source((port)->cc1)))
+
+#define tcpm_port_is_debug(port) \
+	(tcpm_cc_is_source((port)->cc1) && tcpm_cc_is_source((port)->cc2))
+
+#define tcpm_port_is_audio(port) \
+	(tcpm_cc_is_audio((port)->cc1) && tcpm_cc_is_audio((port)->cc2))
+
+#define tcpm_port_is_audio_detached(port) \
+	((tcpm_cc_is_audio((port)->cc1) && tcpm_cc_is_open((port)->cc2)) || \
+	 (tcpm_cc_is_audio((port)->cc2) && tcpm_cc_is_open((port)->cc1)))
+
+#define tcpm_try_snk(port) \
+	((port)->try_snk_count == 0 && (port)->try_role == TYPEC_SINK)
+
+#define tcpm_try_src(port) \
+	((port)->try_src_count == 0 && (port)->try_role == TYPEC_SOURCE)
+
+static enum tcpm_state tcpm_default_state(struct tcpm_port *port)
+{
+	if (port->try_role == TYPEC_SINK)
+		return SNK_UNATTACHED;
+	else if (port->try_role == TYPEC_SOURCE)
+		return SRC_UNATTACHED;
+	else if (port->tcpc->config->default_role == TYPEC_SINK)
+		return SNK_UNATTACHED;
+	return SRC_UNATTACHED;
+}
+
+static inline
+struct tcpm_port *typec_cap_to_tcpm(const struct typec_capability *cap)
+{
+	return container_of(cap, struct tcpm_port, typec_caps);
+}
+
+static bool tcpm_port_is_disconnected(struct tcpm_port *port)
+{
+	return (!port->attached && port->cc1 == TYPEC_CC_OPEN &&
+		port->cc2 == TYPEC_CC_OPEN) ||
+	       (port->attached && ((port->polarity == TYPEC_POLARITY_CC1 &&
+				    port->cc1 == TYPEC_CC_OPEN) ||
+				   (port->polarity == TYPEC_POLARITY_CC2 &&
+				    port->cc2 == TYPEC_CC_OPEN)));
+}
+
+/*
+ * Logging
+ */
+
+#ifdef CONFIG_DEBUG_FS
+
+static bool tcpm_log_full(struct tcpm_port *port)
+{
+	return port->logbuffer_tail ==
+		(port->logbuffer_head + 1) % LOG_BUFFER_ENTRIES;
+}
+
+static void _tcpm_log(struct tcpm_port *port, const char *fmt, va_list args)
+{
+	char tmpbuffer[LOG_BUFFER_ENTRY_SIZE];
+	u64 ts_nsec = local_clock();
+	unsigned long rem_nsec;
+
+	if (!port->logbuffer[port->logbuffer_head]) {
+		port->logbuffer[port->logbuffer_head] =
+				kzalloc(LOG_BUFFER_ENTRY_SIZE, GFP_KERNEL);
+		if (!port->logbuffer[port->logbuffer_head])
+			return;
+	}
+
+	vsnprintf(tmpbuffer, sizeof(tmpbuffer), fmt, args);
+
+	mutex_lock(&port->logbuffer_lock);
+
+	if (tcpm_log_full(port)) {
+		port->logbuffer_head = max(port->logbuffer_head - 1, 0);
+		strcpy(tmpbuffer, "overflow");
+	}
+
+	if (port->logbuffer_head < 0 ||
+	    port->logbuffer_head >= LOG_BUFFER_ENTRIES) {
+		dev_warn(port->dev,
+			 "Bad log buffer index %d\n", port->logbuffer_head);
+		goto abort;
+	}
+
+	if (!port->logbuffer[port->logbuffer_head]) {
+		dev_warn(port->dev,
+			 "Log buffer index %d is NULL\n", port->logbuffer_head);
+		goto abort;
+	}
+
+	rem_nsec = do_div(ts_nsec, 1000000000);
+	scnprintf(port->logbuffer[port->logbuffer_head],
+		  LOG_BUFFER_ENTRY_SIZE, "[%5lu.%06lu] %s",
+		  (unsigned long)ts_nsec, rem_nsec / 1000,
+		  tmpbuffer);
+	port->logbuffer_head = (port->logbuffer_head + 1) % LOG_BUFFER_ENTRIES;
+
+abort:
+	mutex_unlock(&port->logbuffer_lock);
+}
+
+static void tcpm_log(struct tcpm_port *port, const char *fmt, ...)
+{
+	va_list args;
+
+	/* Do not log while disconnected and unattached */
+	if (tcpm_port_is_disconnected(port) &&
+	    (port->state == SRC_UNATTACHED || port->state == SNK_UNATTACHED ||
+	     port->state == DRP_TOGGLING))
+		return;
+
+	va_start(args, fmt);
+	_tcpm_log(port, fmt, args);
+	va_end(args);
+}
+
+static void tcpm_log_force(struct tcpm_port *port, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	_tcpm_log(port, fmt, args);
+	va_end(args);
+}
+
+static void tcpm_log_source_caps(struct tcpm_port *port)
+{
+	int i;
+
+	for (i = 0; i < port->nr_source_caps; i++) {
+		u32 pdo = port->source_caps[i];
+		enum pd_pdo_type type = pdo_type(pdo);
+		char msg[64];
+
+		switch (type) {
+		case PDO_TYPE_FIXED:
+			scnprintf(msg, sizeof(msg),
+				  "%u mV, %u mA [%s%s%s%s%s%s]",
+				  pdo_fixed_voltage(pdo),
+				  pdo_max_current(pdo),
+				  (pdo & PDO_FIXED_DUAL_ROLE) ?
+							"R" : "",
+				  (pdo & PDO_FIXED_SUSPEND) ?
+							"S" : "",
+				  (pdo & PDO_FIXED_HIGHER_CAP) ?
+							"H" : "",
+				  (pdo & PDO_FIXED_USB_COMM) ?
+							"U" : "",
+				  (pdo & PDO_FIXED_DATA_SWAP) ?
+							"D" : "",
+				  (pdo & PDO_FIXED_EXTPOWER) ?
+							"E" : "");
+			break;
+		case PDO_TYPE_VAR:
+			scnprintf(msg, sizeof(msg),
+				  "%u-%u mV, %u mA",
+				  pdo_min_voltage(pdo),
+				  pdo_max_voltage(pdo),
+				  pdo_max_current(pdo));
+			break;
+		case PDO_TYPE_BATT:
+			scnprintf(msg, sizeof(msg),
+				  "%u-%u mV, %u mW",
+				  pdo_min_voltage(pdo),
+				  pdo_max_voltage(pdo),
+				  pdo_max_power(pdo));
+			break;
+		default:
+			strcpy(msg, "undefined");
+			break;
+		}
+		tcpm_log(port, " PDO %d: type %d, %s",
+			 i, type, msg);
+	}
+}
+
+static int tcpm_seq_show(struct seq_file *s, void *v)
+{
+	struct tcpm_port *port = (struct tcpm_port *)s->private;
+	int tail;
+
+	mutex_lock(&port->logbuffer_lock);
+	tail = port->logbuffer_tail;
+	while (tail != port->logbuffer_head) {
+		seq_printf(s, "%s\n", port->logbuffer[tail]);
+		tail = (tail + 1) % LOG_BUFFER_ENTRIES;
+	}
+	if (!seq_has_overflowed(s))
+		port->logbuffer_tail = tail;
+	mutex_unlock(&port->logbuffer_lock);
+
+	return 0;
+}
+
+static int tcpm_debug_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, tcpm_seq_show, inode->i_private);
+}
+
+static const struct file_operations tcpm_debug_operations = {
+	.open		= tcpm_debug_open,
+	.llseek		= seq_lseek,
+	.read		= seq_read,
+	.release	= single_release,
+};
+
+static struct dentry *rootdir;
+
+static int tcpm_debugfs_init(struct tcpm_port *port)
+{
+	mutex_init(&port->logbuffer_lock);
+	/* /sys/kernel/debug/tcpm/usbcX */
+	if (!rootdir) {
+		rootdir = debugfs_create_dir("tcpm", NULL);
+		if (!rootdir)
+			return -ENOMEM;
+	}
+
+	port->dentry = debugfs_create_file(dev_name(port->dev),
+					   S_IFREG | 0444, rootdir,
+					   port, &tcpm_debug_operations);
+
+	return 0;
+}
+
+static void tcpm_debugfs_exit(struct tcpm_port *port)
+{
+	debugfs_remove(port->dentry);
+}
+
+#else
+
+static void tcpm_log(const struct tcpm_port *port, const char *fmt, ...) { }
+static void tcpm_log_force(struct tcpm_port *port, const char *fmt, ...) { }
+static void tcpm_log_source_caps(struct tcpm_port *port) { }
+static int tcpm_debugfs_init(const struct tcpm_port *port) { return 0; }
+static void tcpm_debugfs_exit(const struct tcpm_port *port) { }
+
+#endif
+
+static int tcpm_pd_transmit(struct tcpm_port *port,
+			    enum tcpm_transmit_type type,
+			    const struct pd_message *msg)
+{
+	unsigned long timeout;
+	int ret;
+
+	if (msg)
+		tcpm_log(port, "PD TX, header: %#x", le16_to_cpu(msg->header));
+	else
+		tcpm_log(port, "PD TX, type: %#x", type);
+
+	reinit_completion(&port->tx_complete);
+	ret = port->tcpc->pd_transmit(port->tcpc, type, msg);
+	if (ret < 0)
+		return ret;
+
+	mutex_unlock(&port->lock);
+	timeout = wait_for_completion_timeout(&port->tx_complete,
+				msecs_to_jiffies(PD_T_TCPC_TX_TIMEOUT));
+	mutex_lock(&port->lock);
+	if (!timeout)
+		return -ETIMEDOUT;
+
+	switch (port->tx_status) {
+	case TCPC_TX_SUCCESS:
+		port->message_id = (port->message_id + 1) & PD_HEADER_ID_MASK;
+		return 0;
+	case TCPC_TX_DISCARDED:
+		return -EAGAIN;
+	case TCPC_TX_FAILED:
+	default:
+		return -EIO;
+	}
+}
+
+void tcpm_pd_transmit_complete(struct tcpm_port *port,
+			       enum tcpm_transmit_status status)
+{
+	tcpm_log(port, "PD TX complete, status: %u", status);
+	port->tx_status = status;
+	complete(&port->tx_complete);
+}
+EXPORT_SYMBOL_GPL(tcpm_pd_transmit_complete);
+
+static int tcpm_mux_set(struct tcpm_port *port, enum tcpc_mux_mode mode,
+			enum tcpc_usb_switch config)
+{
+	int ret = 0;
+
+	tcpm_log(port, "Requesting mux mode %d, config %d, polarity %d",
+		 mode, config, port->polarity);
+
+	if (port->tcpc->mux)
+		ret = port->tcpc->mux->set(port->tcpc->mux, mode, config,
+					   port->polarity);
+
+	return ret;
+}
+
+static int tcpm_set_polarity(struct tcpm_port *port,
+			     enum typec_cc_polarity polarity)
+{
+	int ret;
+
+	tcpm_log(port, "polarity %d", polarity);
+
+	ret = port->tcpc->set_polarity(port->tcpc, polarity);
+	if (ret < 0)
+		return ret;
+
+	port->polarity = polarity;
+
+	return 0;
+}
+
+static int tcpm_set_vconn(struct tcpm_port *port, bool enable)
+{
+	int ret;
+
+	tcpm_log(port, "vconn:=%d", enable);
+
+	ret = port->tcpc->set_vconn(port->tcpc, enable);
+	if (!ret) {
+		port->vconn_role = enable ? TYPEC_SOURCE : TYPEC_SINK;
+		typec_set_vconn_role(port->typec_port, port->vconn_role);
+	}
+
+	return ret;
+}
+
+static u32 tcpm_get_current_limit(struct tcpm_port *port)
+{
+	enum typec_cc_status cc;
+	u32 limit;
+
+	cc = port->polarity ? port->cc2 : port->cc1;
+	switch (cc) {
+	case TYPEC_CC_RP_1_5:
+		limit = 1500;
+		break;
+	case TYPEC_CC_RP_3_0:
+		limit = 3000;
+		break;
+	case TYPEC_CC_RP_DEF:
+	default:
+		limit = 0;
+		break;
+	}
+
+	return limit;
+}
+
+static int tcpm_set_current_limit(struct tcpm_port *port, u32 max_ma, u32 mv)
+{
+	int ret = -EOPNOTSUPP;
+
+	tcpm_log(port, "Setting voltage/current limit %u mV %u mA", mv, max_ma);
+
+	if (port->tcpc->set_current_limit)
+		ret = port->tcpc->set_current_limit(port->tcpc, max_ma, mv);
+
+	return ret;
+}
+
+/*
+ * Determine RP value to set based on maximum current supported
+ * by a port if configured as source.
+ * Returns CC value to report to link partner.
+ */
+static enum typec_cc_status tcpm_rp_cc(struct tcpm_port *port)
+{
+	const u32 *src_pdo = port->src_pdo;
+	int nr_pdo = port->nr_src_pdo;
+	int i;
+
+	/*
+	 * Search for first entry with matching voltage.
+	 * It should report the maximum supported current.
+	 */
+	for (i = 0; i < nr_pdo; i++) {
+		const u32 pdo = src_pdo[i];
+
+		if (pdo_type(pdo) == PDO_TYPE_FIXED &&
+		    pdo_fixed_voltage(pdo) == 5000) {
+			unsigned int curr = pdo_max_current(pdo);
+
+			if (curr >= 3000)
+				return TYPEC_CC_RP_3_0;
+			else if (curr >= 1500)
+				return TYPEC_CC_RP_1_5;
+			return TYPEC_CC_RP_DEF;
+		}
+	}
+
+	return TYPEC_CC_RP_DEF;
+}
+
+static int tcpm_set_attached_state(struct tcpm_port *port, bool attached)
+{
+	return port->tcpc->set_roles(port->tcpc, attached, port->pwr_role,
+				     port->data_role);
+}
+
+static int tcpm_set_roles(struct tcpm_port *port, bool attached,
+			  enum typec_role role, enum typec_data_role data)
+{
+	int ret;
+
+	if (data == TYPEC_HOST)
+		ret = tcpm_mux_set(port, TYPEC_MUX_USB,
+				   TCPC_USB_SWITCH_CONNECT);
+	else
+		ret = tcpm_mux_set(port, TYPEC_MUX_NONE,
+				   TCPC_USB_SWITCH_DISCONNECT);
+	if (ret < 0)
+		return ret;
+
+	ret = port->tcpc->set_roles(port->tcpc, attached, role, data);
+	if (ret < 0)
+		return ret;
+
+	port->pwr_role = role;
+	port->data_role = data;
+	typec_set_data_role(port->typec_port, data);
+	typec_set_pwr_role(port->typec_port, role);
+
+	return 0;
+}
+
+static int tcpm_set_pwr_role(struct tcpm_port *port, enum typec_role role)
+{
+	int ret;
+
+	ret = port->tcpc->set_roles(port->tcpc, true, role,
+				    port->data_role);
+	if (ret < 0)
+		return ret;
+
+	port->pwr_role = role;
+	typec_set_pwr_role(port->typec_port, role);
+
+	return 0;
+}
+
+static int tcpm_pd_send_source_caps(struct tcpm_port *port)
+{
+	struct pd_message msg;
+	int i;
+
+	memset(&msg, 0, sizeof(msg));
+	if (!port->nr_src_pdo) {
+		/* No source capabilities defined, sink only */
+		msg.header = PD_HEADER_LE(PD_CTRL_REJECT,
+					  port->pwr_role,
+					  port->data_role,
+					  port->message_id, 0);
+	} else {
+		msg.header = PD_HEADER_LE(PD_DATA_SOURCE_CAP,
+					  port->pwr_role,
+					  port->data_role,
+					  port->message_id,
+					  port->nr_src_pdo);
+	}
+	for (i = 0; i < port->nr_src_pdo; i++)
+		msg.payload[i] = cpu_to_le32(port->src_pdo[i]);
+
+	return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg);
+}
+
+static int tcpm_pd_send_sink_caps(struct tcpm_port *port)
+{
+	struct pd_message msg;
+	int i;
+
+	memset(&msg, 0, sizeof(msg));
+	if (!port->nr_snk_pdo) {
+		/* No sink capabilities defined, source only */
+		msg.header = PD_HEADER_LE(PD_CTRL_REJECT,
+					  port->pwr_role,
+					  port->data_role,
+					  port->message_id, 0);
+	} else {
+		msg.header = PD_HEADER_LE(PD_DATA_SINK_CAP,
+					  port->pwr_role,
+					  port->data_role,
+					  port->message_id,
+					  port->nr_snk_pdo);
+	}
+	for (i = 0; i < port->nr_snk_pdo; i++)
+		msg.payload[i] = cpu_to_le32(port->snk_pdo[i]);
+
+	return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg);
+}
+
+static void tcpm_set_state(struct tcpm_port *port, enum tcpm_state state,
+			   unsigned int delay_ms)
+{
+	if (delay_ms) {
+		tcpm_log(port, "pending state change %s -> %s @ %u ms",
+			 tcpm_states[port->state], tcpm_states[state],
+			 delay_ms);
+		port->delayed_state = state;
+		mod_delayed_work(port->wq, &port->state_machine,
+				 msecs_to_jiffies(delay_ms));
+		port->delayed_runtime = jiffies + msecs_to_jiffies(delay_ms);
+		port->delay_ms = delay_ms;
+	} else {
+		tcpm_log(port, "state change %s -> %s",
+			 tcpm_states[port->state], tcpm_states[state]);
+		port->delayed_state = INVALID_STATE;
+		port->prev_state = port->state;
+		port->state = state;
+		/*
+		 * Don't re-queue the state machine work item if we're currently
+		 * in the state machine and we're immediately changing states.
+		 * tcpm_state_machine_work() will continue running the state
+		 * machine.
+		 */
+		if (!port->state_machine_running)
+			mod_delayed_work(port->wq, &port->state_machine, 0);
+	}
+}
+
+static void tcpm_set_state_cond(struct tcpm_port *port, enum tcpm_state state,
+				unsigned int delay_ms)
+{
+	if (port->enter_state == port->state)
+		tcpm_set_state(port, state, delay_ms);
+	else
+		tcpm_log(port,
+			 "skipped %sstate change %s -> %s [%u ms], context state %s",
+			 delay_ms ? "delayed " : "",
+			 tcpm_states[port->state], tcpm_states[state],
+			 delay_ms, tcpm_states[port->enter_state]);
+}
+
+static void tcpm_queue_message(struct tcpm_port *port,
+			       enum pd_msg_request message)
+{
+	port->queued_message = message;
+	mod_delayed_work(port->wq, &port->state_machine, 0);
+}
+
+/*
+ * VDM/VDO handling functions
+ */
+static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header,
+			   const u32 *data, int cnt)
+{
+	port->vdo_count = cnt + 1;
+	port->vdo_data[0] = header;
+	memcpy(&port->vdo_data[1], data, sizeof(u32) * cnt);
+	/* Set ready, vdm state machine will actually send */
+	port->vdm_retries = 0;
+	port->vdm_state = VDM_STATE_READY;
+}
+
+static void svdm_consume_identity(struct tcpm_port *port, const __le32 *payload,
+				  int cnt)
+{
+	u32 vdo = le32_to_cpu(payload[VDO_INDEX_IDH]);
+	u32 product = le32_to_cpu(payload[VDO_INDEX_PRODUCT]);
+
+	memset(&port->mode_data, 0, sizeof(port->mode_data));
+
+#if 0 /* Not really a match */
+	switch (PD_IDH_PTYPE(vdo)) {
+	case IDH_PTYPE_UNDEF:
+		port->partner.type = TYPEC_PARTNER_NONE; /* no longer exists */
+		break;
+	case IDH_PTYPE_HUB:
+		break;
+	case IDH_PTYPE_PERIPH:
+		break;
+	case IDH_PTYPE_PCABLE:
+		break;
+	case IDH_PTYPE_ACABLE:
+		break;
+	case IDH_PTYPE_AMA:
+		port->partner.type = TYPEC_PARTNER_ALTMODE;
+		break;
+	default:
+		break;
+	}
+#endif
+
+	port->partner_ident.id_header = vdo;
+	port->partner_ident.cert_stat = le32_to_cpu(payload[VDO_INDEX_CSTAT]);
+	port->partner_ident.product = product;
+
+	typec_partner_set_identity(port->partner);
+
+	tcpm_log(port, "Identity: %04x:%04x.%04x",
+		 PD_IDH_VID(vdo),
+		 PD_PRODUCT_PID(product), product & 0xffff);
+}
+
+static bool svdm_consume_svids(struct tcpm_port *port, const __le32 *payload,
+			       int cnt)
+{
+	struct pd_mode_data *pmdata = &port->mode_data;
+	int i;
+
+	for (i = 1; i < cnt; i++) {
+		u32 p = le32_to_cpu(payload[i]);
+		u16 svid;
+
+		svid = (p >> 16) & 0xffff;
+		if (!svid)
+			return false;
+
+		if (pmdata->nsvids >= SVID_DISCOVERY_MAX)
+			goto abort;
+
+		pmdata->svids[pmdata->nsvids++] = svid;
+		tcpm_log(port, "SVID %d: 0x%x", pmdata->nsvids, svid);
+
+		svid = p & 0xffff;
+		if (!svid)
+			return false;
+
+		if (pmdata->nsvids >= SVID_DISCOVERY_MAX)
+			goto abort;
+
+		pmdata->svids[pmdata->nsvids++] = svid;
+		tcpm_log(port, "SVID %d: 0x%x", pmdata->nsvids, svid);
+	}
+	return true;
+abort:
+	tcpm_log(port, "SVID_DISCOVERY_MAX(%d) too low!", SVID_DISCOVERY_MAX);
+	return false;
+}
+
+static void svdm_consume_modes(struct tcpm_port *port, const __le32 *payload,
+			       int cnt)
+{
+	struct pd_mode_data *pmdata = &port->mode_data;
+	struct typec_altmode_desc *paltmode;
+	struct typec_mode_desc *pmode;
+	int i;
+
+	if (pmdata->altmodes >= ARRAY_SIZE(port->partner_altmode)) {
+		/* Already logged in svdm_consume_svids() */
+		return;
+	}
+
+	paltmode = &pmdata->altmode_desc[pmdata->altmodes];
+	memset(paltmode, 0, sizeof(*paltmode));
+
+	paltmode->svid = pmdata->svids[pmdata->svid_index];
+
+	tcpm_log(port, " Alternate mode %d: SVID 0x%04x",
+		 pmdata->altmodes, paltmode->svid);
+
+	for (i = 1; i < cnt && paltmode->n_modes < ALTMODE_MAX_MODES; i++) {
+		pmode = &paltmode->modes[paltmode->n_modes];
+		memset(pmode, 0, sizeof(*pmode));
+		pmode->vdo = le32_to_cpu(payload[i]);
+		pmode->index = i - 1;
+		paltmode->n_modes++;
+		tcpm_log(port, "  VDO %d: 0x%08x",
+			 pmode->index, pmode->vdo);
+	}
+	port->partner_altmode[pmdata->altmodes] =
+		typec_partner_register_altmode(port->partner, paltmode);
+	if (port->partner_altmode[pmdata->altmodes] == NULL) {
+		tcpm_log(port,
+			 "Failed to register alternate modes for SVID 0x%04x",
+			 paltmode->svid);
+		return;
+	}
+	pmdata->altmodes++;
+}
+
+#define supports_modal(port)	PD_IDH_MODAL_SUPP((port)->partner_ident.id_header)
+
+static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt,
+			u32 *response)
+{
+	u32 p0 = le32_to_cpu(payload[0]);
+	int cmd_type = PD_VDO_CMDT(p0);
+	int cmd = PD_VDO_CMD(p0);
+	struct pd_mode_data *modep;
+	int rlen = 0;
+	u16 svid;
+
+	tcpm_log(port, "Rx VDM cmd 0x%x type %d cmd %d len %d",
+		 p0, cmd_type, cmd, cnt);
+
+	modep = &port->mode_data;
+
+	switch (cmd_type) {
+	case CMDT_INIT:
+		switch (cmd) {
+		case CMD_DISCOVER_IDENT:
+			break;
+		case CMD_DISCOVER_SVID:
+			break;
+		case CMD_DISCOVER_MODES:
+			break;
+		case CMD_ENTER_MODE:
+			break;
+		case CMD_EXIT_MODE:
+			break;
+		case CMD_ATTENTION:
+			break;
+		default:
+			break;
+		}
+		if (rlen >= 1) {
+			response[0] = p0 | VDO_CMDT(CMDT_RSP_ACK);
+		} else if (rlen == 0) {
+			response[0] = p0 | VDO_CMDT(CMDT_RSP_NAK);
+			rlen = 1;
+		} else {
+			response[0] = p0 | VDO_CMDT(CMDT_RSP_BUSY);
+			rlen = 1;
+		}
+		break;
+	case CMDT_RSP_ACK:
+		/* silently drop message if we are not connected */
+		if (!port->partner)
+			break;
+
+		switch (cmd) {
+		case CMD_DISCOVER_IDENT:
+			/* 6.4.4.3.1 */
+			svdm_consume_identity(port, payload, cnt);
+			response[0] = VDO(USB_SID_PD, 1, CMD_DISCOVER_SVID);
+			rlen = 1;
+			break;
+		case CMD_DISCOVER_SVID:
+			/* 6.4.4.3.2 */
+			if (svdm_consume_svids(port, payload, cnt)) {
+				response[0] = VDO(USB_SID_PD, 1,
+						  CMD_DISCOVER_SVID);
+				rlen = 1;
+			} else if (modep->nsvids && supports_modal(port)) {
+				response[0] = VDO(modep->svids[0], 1,
+						  CMD_DISCOVER_MODES);
+				rlen = 1;
+			}
+			break;
+		case CMD_DISCOVER_MODES:
+			/* 6.4.4.3.3 */
+			svdm_consume_modes(port, payload, cnt);
+			modep->svid_index++;
+			if (modep->svid_index < modep->nsvids) {
+				svid = modep->svids[modep->svid_index];
+				response[0] = VDO(svid, 1, CMD_DISCOVER_MODES);
+				rlen = 1;
+			} else {
+#if 0
+				response[0] = pd_dfp_enter_mode(port, 0, 0);
+				if (response[0])
+					rlen = 1;
+#endif
+			}
+			break;
+		case CMD_ENTER_MODE:
+			break;
+		default:
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return rlen;
+}
+
+static void tcpm_handle_vdm_request(struct tcpm_port *port,
+				    const __le32 *payload, int cnt)
+{
+	int rlen = 0;
+	u32 response[8] = { };
+	u32 p0 = le32_to_cpu(payload[0]);
+
+	if (port->vdm_state == VDM_STATE_BUSY) {
+		/* If UFP responded busy retry after timeout */
+		if (PD_VDO_CMDT(p0) == CMDT_RSP_BUSY) {
+			port->vdm_state = VDM_STATE_WAIT_RSP_BUSY;
+			port->vdo_retry = (p0 & ~VDO_CMDT_MASK) |
+				CMDT_INIT;
+			mod_delayed_work(port->wq, &port->vdm_state_machine,
+					 msecs_to_jiffies(PD_T_VDM_BUSY));
+			return;
+		}
+		port->vdm_state = VDM_STATE_DONE;
+	}
+
+	if (PD_VDO_SVDM(p0))
+		rlen = tcpm_pd_svdm(port, payload, cnt, response);
+#if 0
+	else
+		rlen = tcpm_pd_custom_vdm(port, cnt, payload, response);
+#endif
+
+	if (rlen > 0) {
+		tcpm_queue_vdm(port, response[0], &response[1], rlen - 1);
+		mod_delayed_work(port->wq, &port->vdm_state_machine, 0);
+	}
+}
+
+static void tcpm_send_vdm(struct tcpm_port *port, u32 vid, int cmd,
+			  const u32 *data, int count)
+{
+	u32 header;
+
+	if (WARN_ON(count > VDO_MAX_SIZE - 1))
+		count = VDO_MAX_SIZE - 1;
+
+	/* set VDM header with VID & CMD */
+	header = VDO(vid, ((vid & USB_SID_PD) == USB_SID_PD) ?
+			1 : (PD_VDO_CMD(cmd) <= CMD_ATTENTION), cmd);
+	tcpm_queue_vdm(port, header, data, count);
+
+	mod_delayed_work(port->wq, &port->vdm_state_machine, 0);
+}
+
+static unsigned int vdm_ready_timeout(u32 vdm_hdr)
+{
+	unsigned int timeout;
+	int cmd = PD_VDO_CMD(vdm_hdr);
+
+	/* its not a structured VDM command */
+	if (!PD_VDO_SVDM(vdm_hdr))
+		return PD_T_VDM_UNSTRUCTURED;
+
+	switch (PD_VDO_CMDT(vdm_hdr)) {
+	case CMDT_INIT:
+		if (cmd == CMD_ENTER_MODE || cmd == CMD_EXIT_MODE)
+			timeout = PD_T_VDM_WAIT_MODE_E;
+		else
+			timeout = PD_T_VDM_SNDR_RSP;
+		break;
+	default:
+		if (cmd == CMD_ENTER_MODE || cmd == CMD_EXIT_MODE)
+			timeout = PD_T_VDM_E_MODE;
+		else
+			timeout = PD_T_VDM_RCVR_RSP;
+		break;
+	}
+	return timeout;
+}
+
+static void vdm_run_state_machine(struct tcpm_port *port)
+{
+	struct pd_message msg;
+	int i, res;
+
+	switch (port->vdm_state) {
+	case VDM_STATE_READY:
+		/* Only transmit VDM if attached */
+		if (!port->attached) {
+			port->vdm_state = VDM_STATE_ERR_BUSY;
+			break;
+		}
+
+		/*
+		 * if there's traffic or we're not in PDO ready state don't send
+		 * a VDM.
+		 */
+		if (port->state != SRC_READY && port->state != SNK_READY)
+			break;
+
+		/* Prepare and send VDM */
+		memset(&msg, 0, sizeof(msg));
+		msg.header = PD_HEADER_LE(PD_DATA_VENDOR_DEF,
+					  port->pwr_role,
+					  port->data_role,
+					  port->message_id, port->vdo_count);
+		for (i = 0; i < port->vdo_count; i++)
+			msg.payload[i] = cpu_to_le32(port->vdo_data[i]);
+		res = tcpm_pd_transmit(port, TCPC_TX_SOP, &msg);
+		if (res < 0) {
+			port->vdm_state = VDM_STATE_ERR_SEND;
+		} else {
+			unsigned long timeout;
+
+			port->vdm_retries = 0;
+			port->vdm_state = VDM_STATE_BUSY;
+			timeout = vdm_ready_timeout(port->vdo_data[0]);
+			mod_delayed_work(port->wq, &port->vdm_state_machine,
+					 timeout);
+		}
+		break;
+	case VDM_STATE_WAIT_RSP_BUSY:
+		port->vdo_data[0] = port->vdo_retry;
+		port->vdo_count = 1;
+		port->vdm_state = VDM_STATE_READY;
+		break;
+	case VDM_STATE_BUSY:
+		port->vdm_state = VDM_STATE_ERR_TMOUT;
+		break;
+	case VDM_STATE_ERR_SEND:
+		/*
+		 * A partner which does not support USB PD will not reply,
+		 * so this is not a fatal error. At the same time, some
+		 * devices may not return GoodCRC under some circumstances,
+		 * so we need to retry.
+		 */
+		if (port->vdm_retries < 3) {
+			tcpm_log(port, "VDM Tx error, retry");
+			port->vdm_retries++;
+			port->vdm_state = VDM_STATE_READY;
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+static void vdm_state_machine_work(struct work_struct *work)
+{
+	struct tcpm_port *port = container_of(work, struct tcpm_port,
+					      vdm_state_machine.work);
+	enum vdm_states prev_state;
+
+	mutex_lock(&port->lock);
+
+	/*
+	 * Continue running as long as the port is not busy and there was
+	 * a state change.
+	 */
+	do {
+		prev_state = port->vdm_state;
+		vdm_run_state_machine(port);
+	} while (port->vdm_state != prev_state &&
+		 port->vdm_state != VDM_STATE_BUSY);
+
+	mutex_unlock(&port->lock);
+}
+
+/*
+ * PD (data, control) command handling functions
+ */
+static void tcpm_pd_data_request(struct tcpm_port *port,
+				 const struct pd_message *msg)
+{
+	enum pd_data_msg_type type = pd_header_type_le(msg->header);
+	unsigned int cnt = pd_header_cnt_le(msg->header);
+	unsigned int i;
+
+	switch (type) {
+	case PD_DATA_SOURCE_CAP:
+		if (port->pwr_role != TYPEC_SINK)
+			break;
+
+		for (i = 0; i < cnt; i++)
+			port->source_caps[i] = le32_to_cpu(msg->payload[i]);
+
+		port->nr_source_caps = cnt;
+
+		tcpm_log_source_caps(port);
+
+		/*
+		 * This message may be received even if VBUS is not
+		 * present. This is quite unexpected; see USB PD
+		 * specification, sections 8.3.3.6.3.1 and 8.3.3.6.3.2.
+		 * However, at the same time, we must be ready to
+		 * receive this message and respond to it 15ms after
+		 * receiving PS_RDY during power swap operations, no matter
+		 * if VBUS is available or not (USB PD specification,
+		 * section 6.5.9.2).
+		 * So we need to accept the message either way,
+		 * but be prepared to keep waiting for VBUS after it was
+		 * handled.
+		 */
+		tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0);
+		break;
+	case PD_DATA_REQUEST:
+		if (port->pwr_role != TYPEC_SOURCE ||
+		    cnt != 1) {
+			tcpm_queue_message(port, PD_MSG_CTRL_REJECT);
+			break;
+		}
+		port->sink_request = le32_to_cpu(msg->payload[0]);
+		tcpm_set_state(port, SRC_NEGOTIATE_CAPABILITIES, 0);
+		break;
+	case PD_DATA_SINK_CAP:
+		/* We don't do anything with this at the moment... */
+		for (i = 0; i < cnt; i++)
+			port->sink_caps[i] = le32_to_cpu(msg->payload[i]);
+		port->nr_sink_caps = cnt;
+		break;
+	case PD_DATA_VENDOR_DEF:
+		tcpm_handle_vdm_request(port, msg->payload, cnt);
+		break;
+	case PD_DATA_BIST:
+		if (port->state == SRC_READY || port->state == SNK_READY) {
+			port->bist_request = le32_to_cpu(msg->payload[0]);
+			tcpm_set_state(port, BIST_RX, 0);
+		}
+		break;
+	default:
+		tcpm_log(port, "Unhandled data message type %#x", type);
+		break;
+	}
+}
+
+static void tcpm_pd_ctrl_request(struct tcpm_port *port,
+				 const struct pd_message *msg)
+{
+	enum pd_ctrl_msg_type type = pd_header_type_le(msg->header);
+	enum tcpm_state next_state;
+
+	switch (type) {
+	case PD_CTRL_GOOD_CRC:
+	case PD_CTRL_PING:
+		break;
+	case PD_CTRL_GET_SOURCE_CAP:
+		switch (port->state) {
+		case SRC_READY:
+		case SNK_READY:
+			tcpm_queue_message(port, PD_MSG_DATA_SOURCE_CAP);
+			break;
+		default:
+			tcpm_queue_message(port, PD_MSG_CTRL_REJECT);
+			break;
+		}
+		break;
+	case PD_CTRL_GET_SINK_CAP:
+		switch (port->state) {
+		case SRC_READY:
+		case SNK_READY:
+			tcpm_queue_message(port, PD_MSG_DATA_SINK_CAP);
+			break;
+		default:
+			tcpm_queue_message(port, PD_MSG_CTRL_REJECT);
+			break;
+		}
+		break;
+	case PD_CTRL_GOTO_MIN:
+		break;
+	case PD_CTRL_PS_RDY:
+		switch (port->state) {
+		case SNK_TRANSITION_SINK:
+			if (port->vbus_present) {
+				tcpm_set_current_limit(port,
+						       port->current_limit,
+						       port->supply_voltage);
+				tcpm_set_state(port, SNK_READY, 0);
+			} else {
+				/*
+				 * Seen after power swap. Keep waiting for VBUS
+				 * in a transitional state.
+				 */
+				tcpm_set_state(port,
+					       SNK_TRANSITION_SINK_VBUS, 0);
+			}
+			break;
+		case PR_SWAP_SRC_SNK_SOURCE_OFF:
+			tcpm_set_state(port, PR_SWAP_SRC_SNK_SINK_ON, 0);
+			break;
+		case PR_SWAP_SNK_SRC_SINK_OFF:
+			tcpm_set_state(port, PR_SWAP_SNK_SRC_SOURCE_ON, 0);
+			break;
+		case VCONN_SWAP_WAIT_FOR_VCONN:
+			tcpm_set_state(port, VCONN_SWAP_TURN_OFF_VCONN, 0);
+			break;
+		default:
+			break;
+		}
+		break;
+	case PD_CTRL_REJECT:
+	case PD_CTRL_WAIT:
+		switch (port->state) {
+		case SNK_NEGOTIATE_CAPABILITIES:
+			/* USB PD specification, Figure 8-43 */
+			if (port->explicit_contract)
+				next_state = SNK_READY;
+			else
+				next_state = SNK_WAIT_CAPABILITIES;
+			tcpm_set_state(port, next_state, 0);
+			break;
+		case DR_SWAP_SEND:
+			port->swap_status = (type == PD_CTRL_WAIT ?
+					     -EAGAIN : -EOPNOTSUPP);
+			tcpm_set_state(port, DR_SWAP_CANCEL, 0);
+			break;
+		case PR_SWAP_SEND:
+			port->swap_status = (type == PD_CTRL_WAIT ?
+					     -EAGAIN : -EOPNOTSUPP);
+			tcpm_set_state(port, PR_SWAP_CANCEL, 0);
+			break;
+		case VCONN_SWAP_SEND:
+			port->swap_status = (type == PD_CTRL_WAIT ?
+					     -EAGAIN : -EOPNOTSUPP);
+			tcpm_set_state(port, VCONN_SWAP_CANCEL, 0);
+			break;
+		default:
+			break;
+		}
+		break;
+	case PD_CTRL_ACCEPT:
+		switch (port->state) {
+		case SNK_NEGOTIATE_CAPABILITIES:
+			tcpm_set_state(port, SNK_TRANSITION_SINK, 0);
+			break;
+		case SOFT_RESET_SEND:
+			port->message_id = 0;
+			if (port->pwr_role == TYPEC_SOURCE)
+				next_state = SRC_SEND_CAPABILITIES;
+			else
+				next_state = SNK_WAIT_CAPABILITIES;
+			tcpm_set_state(port, next_state, 0);
+			break;
+		case DR_SWAP_SEND:
+			tcpm_set_state(port, DR_SWAP_CHANGE_DR, 0);
+			break;
+		case PR_SWAP_SEND:
+			tcpm_set_state(port, PR_SWAP_START, 0);
+			break;
+		case VCONN_SWAP_SEND:
+			tcpm_set_state(port, VCONN_SWAP_START, 0);
+			break;
+		default:
+			break;
+		}
+		break;
+	case PD_CTRL_SOFT_RESET:
+		tcpm_set_state(port, SOFT_RESET, 0);
+		break;
+	case PD_CTRL_DR_SWAP:
+		if (port->typec_caps.type != TYPEC_PORT_DRP) {
+			tcpm_queue_message(port, PD_MSG_CTRL_REJECT);
+			break;
+		}
+		/*
+		 * XXX
+		 * 6.3.9: If an alternate mode is active, a request to swap
+		 * alternate modes shall trigger a port reset.
+		 */
+		switch (port->state) {
+		case SRC_READY:
+		case SNK_READY:
+			tcpm_set_state(port, DR_SWAP_ACCEPT, 0);
+			break;
+		default:
+			tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
+			break;
+		}
+		break;
+	case PD_CTRL_PR_SWAP:
+		if (port->typec_caps.type != TYPEC_PORT_DRP) {
+			tcpm_queue_message(port, PD_MSG_CTRL_REJECT);
+			break;
+		}
+		switch (port->state) {
+		case SRC_READY:
+		case SNK_READY:
+			tcpm_set_state(port, PR_SWAP_ACCEPT, 0);
+			break;
+		default:
+			tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
+			break;
+		}
+		break;
+	case PD_CTRL_VCONN_SWAP:
+		switch (port->state) {
+		case SRC_READY:
+		case SNK_READY:
+			tcpm_set_state(port, VCONN_SWAP_ACCEPT, 0);
+			break;
+		default:
+			tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
+			break;
+		}
+		break;
+	default:
+		tcpm_log(port, "Unhandled ctrl message type %#x", type);
+		break;
+	}
+}
+
+static void tcpm_pd_rx_handler(struct work_struct *work)
+{
+	struct pd_rx_event *event = container_of(work,
+						 struct pd_rx_event, work);
+	const struct pd_message *msg = &event->msg;
+	unsigned int cnt = pd_header_cnt_le(msg->header);
+	struct tcpm_port *port = event->port;
+
+	mutex_lock(&port->lock);
+
+	tcpm_log(port, "PD RX, header: %#x [%d]", le16_to_cpu(msg->header),
+		 port->attached);
+
+	if (port->attached) {
+		/*
+		 * If both ends believe to be DFP/host, we have a data role
+		 * mismatch.
+		 */
+		if (!!(le16_to_cpu(msg->header) & PD_HEADER_DATA_ROLE) ==
+		    (port->data_role == TYPEC_HOST)) {
+			tcpm_log(port,
+				 "Data role mismatch, initiating error recovery");
+			tcpm_set_state(port, ERROR_RECOVERY, 0);
+		} else {
+			if (cnt)
+				tcpm_pd_data_request(port, msg);
+			else
+				tcpm_pd_ctrl_request(port, msg);
+		}
+	}
+
+	mutex_unlock(&port->lock);
+	kfree(event);
+}
+
+void tcpm_pd_receive(struct tcpm_port *port, const struct pd_message *msg)
+{
+	struct pd_rx_event *event;
+
+	event = kzalloc(sizeof(*event), GFP_ATOMIC);
+	if (!event)
+		return;
+
+	INIT_WORK(&event->work, tcpm_pd_rx_handler);
+	event->port = port;
+	memcpy(&event->msg, msg, sizeof(*msg));
+	queue_work(port->wq, &event->work);
+}
+EXPORT_SYMBOL_GPL(tcpm_pd_receive);
+
+static int tcpm_pd_send_control(struct tcpm_port *port,
+				enum pd_ctrl_msg_type type)
+{
+	struct pd_message msg;
+
+	memset(&msg, 0, sizeof(msg));
+	msg.header = PD_HEADER_LE(type, port->pwr_role,
+				  port->data_role,
+				  port->message_id, 0);
+
+	return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg);
+}
+
+/*
+ * Send queued message without affecting state.
+ * Return true if state machine should go back to sleep,
+ * false otherwise.
+ */
+static bool tcpm_send_queued_message(struct tcpm_port *port)
+{
+	enum pd_msg_request queued_message;
+
+	do {
+		queued_message = port->queued_message;
+		port->queued_message = PD_MSG_NONE;
+
+		switch (queued_message) {
+		case PD_MSG_CTRL_WAIT:
+			tcpm_pd_send_control(port, PD_CTRL_WAIT);
+			break;
+		case PD_MSG_CTRL_REJECT:
+			tcpm_pd_send_control(port, PD_CTRL_REJECT);
+			break;
+		case PD_MSG_DATA_SINK_CAP:
+			tcpm_pd_send_sink_caps(port);
+			break;
+		case PD_MSG_DATA_SOURCE_CAP:
+			tcpm_pd_send_source_caps(port);
+			break;
+		default:
+			break;
+		}
+	} while (port->queued_message != PD_MSG_NONE);
+
+	if (port->delayed_state != INVALID_STATE) {
+		if (time_is_after_jiffies(port->delayed_runtime)) {
+			mod_delayed_work(port->wq, &port->state_machine,
+					 port->delayed_runtime - jiffies);
+			return true;
+		}
+		port->delayed_state = INVALID_STATE;
+	}
+	return false;
+}
+
+static int tcpm_pd_check_request(struct tcpm_port *port)
+{
+	u32 pdo, rdo = port->sink_request;
+	unsigned int max, op, pdo_max, index;
+	enum pd_pdo_type type;
+
+	index = rdo_index(rdo);
+	if (!index || index > port->nr_src_pdo)
+		return -EINVAL;
+
+	pdo = port->src_pdo[index - 1];
+	type = pdo_type(pdo);
+	switch (type) {
+	case PDO_TYPE_FIXED:
+	case PDO_TYPE_VAR:
+		max = rdo_max_current(rdo);
+		op = rdo_op_current(rdo);
+		pdo_max = pdo_max_current(pdo);
+
+		if (op > pdo_max)
+			return -EINVAL;
+		if (max > pdo_max && !(rdo & RDO_CAP_MISMATCH))
+			return -EINVAL;
+
+		if (type == PDO_TYPE_FIXED)
+			tcpm_log(port,
+				 "Requested %u mV, %u mA for %u / %u mA",
+				 pdo_fixed_voltage(pdo), pdo_max, op, max);
+		else
+			tcpm_log(port,
+				 "Requested %u -> %u mV, %u mA for %u / %u mA",
+				 pdo_min_voltage(pdo), pdo_max_voltage(pdo),
+				 pdo_max, op, max);
+		break;
+	case PDO_TYPE_BATT:
+		max = rdo_max_power(rdo);
+		op = rdo_op_power(rdo);
+		pdo_max = pdo_max_power(pdo);
+
+		if (op > pdo_max)
+			return -EINVAL;
+		if (max > pdo_max && !(rdo & RDO_CAP_MISMATCH))
+			return -EINVAL;
+		tcpm_log(port,
+			 "Requested %u -> %u mV, %u mW for %u / %u mW",
+			 pdo_min_voltage(pdo), pdo_max_voltage(pdo),
+			 pdo_max, op, max);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	port->op_vsafe5v = index == 1;
+
+	return 0;
+}
+
+static int tcpm_pd_select_pdo(struct tcpm_port *port)
+{
+	unsigned int i, max_mw = 0, max_mv = 0;
+	int ret = -EINVAL;
+
+	/*
+	 * Select the source PDO providing the most power while staying within
+	 * the board's voltage limits. Prefer PDO providing exp
+	 */
+	for (i = 0; i < port->nr_source_caps; i++) {
+		u32 pdo = port->source_caps[i];
+		enum pd_pdo_type type = pdo_type(pdo);
+		unsigned int mv, ma, mw;
+
+		if (type == PDO_TYPE_FIXED)
+			mv = pdo_fixed_voltage(pdo);
+		else
+			mv = pdo_min_voltage(pdo);
+
+		if (type == PDO_TYPE_BATT) {
+			mw = pdo_max_power(pdo);
+		} else {
+			ma = min(pdo_max_current(pdo),
+				 port->max_snk_ma);
+			mw = ma * mv / 1000;
+		}
+
+		/* Perfer higher voltages if available */
+		if ((mw > max_mw || (mw == max_mw && mv > max_mv)) &&
+		    mv <= port->max_snk_mv) {
+			ret = i;
+			max_mw = mw;
+			max_mv = mv;
+		}
+	}
+
+	return ret;
+}
+
+static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
+{
+	unsigned int mv, ma, mw, flags;
+	unsigned int max_ma, max_mw;
+	enum pd_pdo_type type;
+	int index;
+	u32 pdo;
+
+	index = tcpm_pd_select_pdo(port);
+	if (index < 0)
+		return -EINVAL;
+	pdo = port->source_caps[index];
+	type = pdo_type(pdo);
+
+	if (type == PDO_TYPE_FIXED)
+		mv = pdo_fixed_voltage(pdo);
+	else
+		mv = pdo_min_voltage(pdo);
+
+	/* Select maximum available current within the board's power limit */
+	if (type == PDO_TYPE_BATT) {
+		mw = pdo_max_power(pdo);
+		ma = 1000 * min(mw, port->max_snk_mw) / mv;
+	} else {
+		ma = min(pdo_max_current(pdo),
+			 1000 * port->max_snk_mw / mv);
+	}
+	ma = min(ma, port->max_snk_ma);
+
+	/* XXX: Any other flags need to be set? */
+	flags = 0;
+
+	/* Set mismatch bit if offered power is less than operating power */
+	mw = ma * mv / 1000;
+	max_ma = ma;
+	max_mw = mw;
+	if (mw < port->operating_snk_mw) {
+		flags |= RDO_CAP_MISMATCH;
+		max_mw = port->operating_snk_mw;
+		max_ma = max_mw * 1000 / mv;
+	}
+
+	tcpm_log(port, "cc=%d cc1=%d cc2=%d vbus=%d vconn=%s polarity=%d",
+		 port->cc_req, port->cc1, port->cc2, port->vbus_source,
+		 port->vconn_role == TYPEC_SOURCE ? "source" : "sink",
+		 port->polarity);
+
+	if (type == PDO_TYPE_BATT) {
+		*rdo = RDO_BATT(index + 1, mw, max_mw, flags);
+
+		tcpm_log(port, "Requesting PDO %d: %u mV, %u mW%s",
+			 index, mv, mw,
+			 flags & RDO_CAP_MISMATCH ? " [mismatch]" : "");
+	} else {
+		*rdo = RDO_FIXED(index + 1, ma, max_ma, flags);
+
+		tcpm_log(port, "Requesting PDO %d: %u mV, %u mA%s",
+			 index, mv, ma,
+			 flags & RDO_CAP_MISMATCH ? " [mismatch]" : "");
+	}
+
+	port->current_limit = ma;
+	port->supply_voltage = mv;
+
+	return 0;
+}
+
+static int tcpm_pd_send_request(struct tcpm_port *port)
+{
+	struct pd_message msg;
+	int ret;
+	u32 rdo;
+
+	ret = tcpm_pd_build_request(port, &rdo);
+	if (ret < 0)
+		return ret;
+
+	memset(&msg, 0, sizeof(msg));
+	msg.header = PD_HEADER_LE(PD_DATA_REQUEST,
+				  port->pwr_role,
+				  port->data_role,
+				  port->message_id, 1);
+	msg.payload[0] = cpu_to_le32(rdo);
+
+	return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg);
+}
+
+static int tcpm_set_vbus(struct tcpm_port *port, bool enable)
+{
+	int ret;
+
+	if (enable && port->vbus_charge)
+		return -EINVAL;
+
+	tcpm_log(port, "vbus:=%d charge=%d", enable, port->vbus_charge);
+
+	ret = port->tcpc->set_vbus(port->tcpc, enable, port->vbus_charge);
+	if (ret < 0)
+		return ret;
+
+	port->vbus_source = enable;
+	return 0;
+}
+
+static int tcpm_set_charge(struct tcpm_port *port, bool charge)
+{
+	int ret;
+
+	if (charge && port->vbus_source)
+		return -EINVAL;
+
+	if (charge != port->vbus_charge) {
+		tcpm_log(port, "vbus=%d charge:=%d", port->vbus_source, charge);
+		ret = port->tcpc->set_vbus(port->tcpc, port->vbus_source,
+					   charge);
+		if (ret < 0)
+			return ret;
+	}
+	port->vbus_charge = charge;
+	return 0;
+}
+
+static bool tcpm_start_drp_toggling(struct tcpm_port *port)
+{
+	int ret;
+
+	if (port->tcpc->start_drp_toggling &&
+	    port->typec_caps.type == TYPEC_PORT_DRP) {
+		tcpm_log_force(port, "Start DRP toggling");
+		ret = port->tcpc->start_drp_toggling(port->tcpc,
+						     tcpm_rp_cc(port));
+		if (!ret)
+			return true;
+	}
+
+	return false;
+}
+
+static void tcpm_set_cc(struct tcpm_port *port, enum typec_cc_status cc)
+{
+	tcpm_log(port, "cc:=%d", cc);
+	port->cc_req = cc;
+	port->tcpc->set_cc(port->tcpc, cc);
+}
+
+static int tcpm_init_vbus(struct tcpm_port *port)
+{
+	int ret;
+
+	ret = port->tcpc->set_vbus(port->tcpc, false, false);
+	port->vbus_source = false;
+	port->vbus_charge = false;
+	return ret;
+}
+
+static int tcpm_init_vconn(struct tcpm_port *port)
+{
+	int ret;
+
+	ret = port->tcpc->set_vconn(port->tcpc, false);
+	port->vconn_role = TYPEC_SINK;
+	return ret;
+}
+
+static void tcpm_typec_connect(struct tcpm_port *port)
+{
+	if (!port->connected) {
+		/* Make sure we don't report stale identity information */
+		memset(&port->partner_ident, 0, sizeof(port->partner_ident));
+		port->partner_desc.usb_pd = port->pd_capable;
+		if (tcpm_port_is_debug(port))
+			port->partner_desc.accessory = TYPEC_ACCESSORY_DEBUG;
+		else if (tcpm_port_is_audio(port))
+			port->partner_desc.accessory = TYPEC_ACCESSORY_AUDIO;
+		else
+			port->partner_desc.accessory = TYPEC_ACCESSORY_NONE;
+		port->partner = typec_register_partner(port->typec_port,
+						       &port->partner_desc);
+		port->connected = true;
+	}
+}
+
+static int tcpm_src_attach(struct tcpm_port *port)
+{
+	enum typec_cc_polarity polarity =
+				port->cc2 == TYPEC_CC_RD ? TYPEC_POLARITY_CC2
+							 : TYPEC_POLARITY_CC1;
+	int ret;
+
+	if (port->attached)
+		return 0;
+
+	ret = tcpm_set_polarity(port, polarity);
+	if (ret < 0)
+		return ret;
+
+	ret = tcpm_set_roles(port, true, TYPEC_SOURCE, TYPEC_HOST);
+	if (ret < 0)
+		return ret;
+
+	ret = port->tcpc->set_pd_rx(port->tcpc, true);
+	if (ret < 0)
+		goto out_disable_mux;
+
+	/*
+	 * USB Type-C specification, version 1.2,
+	 * chapter 4.5.2.2.8.1 (Attached.SRC Requirements)
+	 * Enable VCONN only if the non-RD port is set to RA.
+	 */
+	if ((polarity == TYPEC_POLARITY_CC1 && port->cc2 == TYPEC_CC_RA) ||
+	    (polarity == TYPEC_POLARITY_CC2 && port->cc1 == TYPEC_CC_RA)) {
+		ret = tcpm_set_vconn(port, true);
+		if (ret < 0)
+			goto out_disable_pd;
+	}
+
+	ret = tcpm_set_vbus(port, true);
+	if (ret < 0)
+		goto out_disable_vconn;
+
+	port->pd_capable = false;
+
+	port->partner = NULL;
+
+	port->attached = true;
+	port->send_discover = true;
+
+	return 0;
+
+out_disable_vconn:
+	tcpm_set_vconn(port, false);
+out_disable_pd:
+	port->tcpc->set_pd_rx(port->tcpc, false);
+out_disable_mux:
+	tcpm_mux_set(port, TYPEC_MUX_NONE, TCPC_USB_SWITCH_DISCONNECT);
+	return ret;
+}
+
+static void tcpm_typec_disconnect(struct tcpm_port *port)
+{
+	if (port->connected) {
+		typec_unregister_partner(port->partner);
+		port->partner = NULL;
+		port->connected = false;
+	}
+}
+
+static void tcpm_unregister_altmodes(struct tcpm_port *port)
+{
+	struct pd_mode_data *modep = &port->mode_data;
+	int i;
+
+	for (i = 0; i < modep->altmodes; i++) {
+		typec_unregister_altmode(port->partner_altmode[i]);
+		port->partner_altmode[i] = NULL;
+	}
+
+	memset(modep, 0, sizeof(*modep));
+}
+
+static void tcpm_reset_port(struct tcpm_port *port)
+{
+	tcpm_unregister_altmodes(port);
+	tcpm_typec_disconnect(port);
+	port->attached = false;
+	port->pd_capable = false;
+
+	port->tcpc->set_pd_rx(port->tcpc, false);
+	tcpm_init_vbus(port);	/* also disables charging */
+	tcpm_init_vconn(port);
+	tcpm_set_current_limit(port, 0, 0);
+	tcpm_set_polarity(port, TYPEC_POLARITY_CC1);
+	tcpm_set_attached_state(port, false);
+	port->try_src_count = 0;
+	port->try_snk_count = 0;
+}
+
+static void tcpm_detach(struct tcpm_port *port)
+{
+	if (!port->attached)
+		return;
+
+	if (tcpm_port_is_disconnected(port))
+		port->hard_reset_count = 0;
+
+	tcpm_reset_port(port);
+}
+
+static void tcpm_src_detach(struct tcpm_port *port)
+{
+	tcpm_detach(port);
+}
+
+static int tcpm_snk_attach(struct tcpm_port *port)
+{
+	int ret;
+
+	if (port->attached)
+		return 0;
+
+	ret = tcpm_set_polarity(port, port->cc2 != TYPEC_CC_OPEN ?
+				TYPEC_POLARITY_CC2 : TYPEC_POLARITY_CC1);
+	if (ret < 0)
+		return ret;
+
+	ret = tcpm_set_roles(port, true, TYPEC_SINK, TYPEC_DEVICE);
+	if (ret < 0)
+		return ret;
+
+	port->pd_capable = false;
+
+	port->partner = NULL;
+
+	port->attached = true;
+	port->send_discover = true;
+
+	return 0;
+}
+
+static void tcpm_snk_detach(struct tcpm_port *port)
+{
+	tcpm_detach(port);
+
+	/* XXX: (Dis)connect SuperSpeed mux? */
+}
+
+static int tcpm_acc_attach(struct tcpm_port *port)
+{
+	int ret;
+
+	if (port->attached)
+		return 0;
+
+	ret = tcpm_set_roles(port, true, TYPEC_SOURCE, TYPEC_HOST);
+	if (ret < 0)
+		return ret;
+
+	port->partner = NULL;
+
+	tcpm_typec_connect(port);
+
+	port->attached = true;
+
+	return 0;
+}
+
+static void tcpm_acc_detach(struct tcpm_port *port)
+{
+	tcpm_detach(port);
+}
+
+static inline enum tcpm_state hard_reset_state(struct tcpm_port *port)
+{
+	if (port->hard_reset_count < PD_N_HARD_RESET_COUNT)
+		return HARD_RESET_SEND;
+	if (port->pd_capable)
+		return ERROR_RECOVERY;
+	if (port->pwr_role == TYPEC_SOURCE)
+		return SRC_UNATTACHED;
+	if (port->state == SNK_WAIT_CAPABILITIES)
+		return SNK_READY;
+	return SNK_UNATTACHED;
+}
+
+static inline enum tcpm_state ready_state(struct tcpm_port *port)
+{
+	if (port->pwr_role == TYPEC_SOURCE)
+		return SRC_READY;
+	else
+		return SNK_READY;
+}
+
+static inline enum tcpm_state unattached_state(struct tcpm_port *port)
+{
+	if (port->pwr_role == TYPEC_SOURCE)
+		return SRC_UNATTACHED;
+	else
+		return SNK_UNATTACHED;
+}
+
+static void tcpm_check_send_discover(struct tcpm_port *port)
+{
+	if (port->data_role == TYPEC_HOST && port->send_discover &&
+	    port->pd_capable) {
+		tcpm_send_vdm(port, USB_SID_PD, CMD_DISCOVER_IDENT, NULL, 0);
+		port->send_discover = false;
+	}
+}
+
+static void tcpm_swap_complete(struct tcpm_port *port, int result)
+{
+	if (port->swap_pending) {
+		port->swap_status = result;
+		port->swap_pending = false;
+		complete(&port->swap_complete);
+	}
+}
+
+static void run_state_machine(struct tcpm_port *port)
+{
+	int ret;
+
+	port->enter_state = port->state;
+	switch (port->state) {
+	case DRP_TOGGLING:
+		break;
+	/* SRC states */
+	case SRC_UNATTACHED:
+		tcpm_swap_complete(port, -ENOTCONN);
+		tcpm_src_detach(port);
+		if (tcpm_start_drp_toggling(port)) {
+			tcpm_set_state(port, DRP_TOGGLING, 0);
+			break;
+		}
+		tcpm_set_cc(port, tcpm_rp_cc(port));
+		if (port->typec_caps.type == TYPEC_PORT_DRP)
+			tcpm_set_state(port, SNK_UNATTACHED, PD_T_DRP_SNK);
+		break;
+	case SRC_ATTACH_WAIT:
+		if (tcpm_port_is_debug(port))
+			tcpm_set_state(port, DEBUG_ACC_ATTACHED,
+				       PD_T_CC_DEBOUNCE);
+		else if (tcpm_port_is_audio(port))
+			tcpm_set_state(port, AUDIO_ACC_ATTACHED,
+				       PD_T_CC_DEBOUNCE);
+		else if (tcpm_port_is_source(port))
+			tcpm_set_state(port,
+				       tcpm_try_snk(port) ? SNK_TRY
+							  : SRC_ATTACHED,
+				       PD_T_CC_DEBOUNCE);
+		break;
+
+	case SNK_TRY:
+		port->try_snk_count++;
+		/*
+		 * Requirements:
+		 * - Do not drive vconn or vbus
+		 * - Terminate CC pins (both) to Rd
+		 * Action:
+		 * - Wait for tDRPTry (PD_T_DRP_TRY).
+		 *   Until then, ignore any state changes.
+		 */
+		tcpm_set_cc(port, TYPEC_CC_RD);
+		tcpm_set_state(port, SNK_TRY_WAIT, PD_T_DRP_TRY);
+		break;
+	case SNK_TRY_WAIT:
+		if (port->vbus_present && tcpm_port_is_sink(port)) {
+			tcpm_set_state(port, SNK_ATTACHED, 0);
+			break;
+		}
+		if (!tcpm_port_is_sink(port)) {
+			tcpm_set_state(port, SRC_TRYWAIT,
+				       PD_T_PD_DEBOUNCE);
+			break;
+		}
+		/* No vbus, cc state is sink or open */
+		tcpm_set_state(port, SRC_TRYWAIT_UNATTACHED, PD_T_DRP_TRYWAIT);
+		break;
+	case SRC_TRYWAIT:
+		tcpm_set_cc(port, tcpm_rp_cc(port));
+		if (!port->vbus_present && tcpm_port_is_source(port))
+			tcpm_set_state(port, SRC_ATTACHED, PD_T_CC_DEBOUNCE);
+		else
+			tcpm_set_state(port, SRC_TRYWAIT_UNATTACHED,
+				       PD_T_DRP_TRY);
+		break;
+	case SRC_TRYWAIT_UNATTACHED:
+		tcpm_set_state(port, SNK_UNATTACHED, 0);
+		break;
+
+	case SRC_ATTACHED:
+		ret = tcpm_src_attach(port);
+		tcpm_set_state(port, SRC_UNATTACHED,
+			       ret < 0 ? 0 : PD_T_PS_SOURCE_ON);
+		break;
+	case SRC_STARTUP:
+		typec_set_pwr_opmode(port->typec_port, TYPEC_PWR_MODE_USB);
+		port->pwr_opmode = TYPEC_PWR_MODE_USB;
+		port->caps_count = 0;
+		port->message_id = 0;
+		port->explicit_contract = false;
+		tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0);
+		break;
+	case SRC_SEND_CAPABILITIES:
+		port->caps_count++;
+		if (port->caps_count > PD_N_CAPS_COUNT) {
+			tcpm_set_state(port, SRC_READY, 0);
+			break;
+		}
+		ret = tcpm_pd_send_source_caps(port);
+		if (ret < 0) {
+			tcpm_set_state(port, SRC_SEND_CAPABILITIES,
+				       PD_T_SEND_SOURCE_CAP);
+		} else {
+			/*
+			 * Per standard, we should clear the reset counter here.
+			 * However, that can result in state machine hang-ups.
+			 * Reset it only in READY state to improve stability.
+			 */
+			/* port->hard_reset_count = 0; */
+			port->caps_count = 0;
+			port->pd_capable = true;
+			tcpm_set_state_cond(port, hard_reset_state(port),
+					    PD_T_SEND_SOURCE_CAP);
+		}
+		break;
+	case SRC_NEGOTIATE_CAPABILITIES:
+		ret = tcpm_pd_check_request(port);
+		if (ret < 0) {
+			tcpm_pd_send_control(port, PD_CTRL_REJECT);
+			if (!port->explicit_contract) {
+				tcpm_set_state(port,
+					       SRC_WAIT_NEW_CAPABILITIES, 0);
+			} else {
+				tcpm_set_state(port, SRC_READY, 0);
+			}
+		} else {
+			tcpm_pd_send_control(port, PD_CTRL_ACCEPT);
+			tcpm_set_state(port, SRC_TRANSITION_SUPPLY,
+				       PD_T_SRC_TRANSITION);
+		}
+		break;
+	case SRC_TRANSITION_SUPPLY:
+		/* XXX: regulator_set_voltage(vbus, ...) */
+		tcpm_pd_send_control(port, PD_CTRL_PS_RDY);
+		port->explicit_contract = true;
+		typec_set_pwr_opmode(port->typec_port, TYPEC_PWR_MODE_PD);
+		port->pwr_opmode = TYPEC_PWR_MODE_PD;
+		tcpm_set_state_cond(port, SRC_READY, 0);
+		break;
+	case SRC_READY:
+#if 1
+		port->hard_reset_count = 0;
+#endif
+		port->try_src_count = 0;
+
+		tcpm_typec_connect(port);
+
+		tcpm_check_send_discover(port);
+		/*
+		 * 6.3.5
+		 * Sending ping messages is not necessary if
+		 * - the source operates at vSafe5V
+		 * or
+		 * - The system is not operating in PD mode
+		 * or
+		 * - Both partners are connected using a Type-C connector
+		 *   XXX How do we know that ?
+		 */
+		if (port->pwr_opmode == TYPEC_PWR_MODE_PD &&
+		    !port->op_vsafe5v) {
+			tcpm_pd_send_control(port, PD_CTRL_PING);
+			tcpm_set_state_cond(port, SRC_READY,
+					    PD_T_SOURCE_ACTIVITY);
+		}
+		break;
+	case SRC_WAIT_NEW_CAPABILITIES:
+		/* Nothing to do... */
+		break;
+
+	/* SNK states */
+	case SNK_UNATTACHED:
+		tcpm_swap_complete(port, -ENOTCONN);
+		tcpm_snk_detach(port);
+		if (tcpm_start_drp_toggling(port)) {
+			tcpm_set_state(port, DRP_TOGGLING, 0);
+			break;
+		}
+		tcpm_set_cc(port, TYPEC_CC_RD);
+		if (port->typec_caps.type == TYPEC_PORT_DRP)
+			tcpm_set_state(port, SRC_UNATTACHED, PD_T_DRP_SRC);
+		break;
+	case SNK_ATTACH_WAIT:
+		if ((port->cc1 == TYPEC_CC_OPEN &&
+		     port->cc2 != TYPEC_CC_OPEN) ||
+		    (port->cc1 != TYPEC_CC_OPEN &&
+		     port->cc2 == TYPEC_CC_OPEN))
+			tcpm_set_state(port, SNK_DEBOUNCED,
+				       PD_T_CC_DEBOUNCE);
+		else if (tcpm_port_is_disconnected(port))
+			tcpm_set_state(port, SNK_UNATTACHED,
+				       PD_T_PD_DEBOUNCE);
+		break;
+	case SNK_DEBOUNCED:
+		if (tcpm_port_is_disconnected(port))
+			tcpm_set_state(port, SNK_UNATTACHED,
+				       PD_T_PD_DEBOUNCE);
+		else if (port->vbus_present)
+			tcpm_set_state(port,
+				       tcpm_try_src(port) ? SRC_TRY
+							  : SNK_ATTACHED,
+				       0);
+		else
+			/* Wait for VBUS, but not forever */
+			tcpm_set_state(port, SNK_UNATTACHED, PD_T_PS_SOURCE_ON);
+		break;
+
+	case SRC_TRY:
+		port->try_src_count++;
+		tcpm_set_cc(port, tcpm_rp_cc(port));
+		tcpm_set_state(port, SNK_TRYWAIT, PD_T_DRP_TRY);
+		break;
+	case SRC_TRY_DEBOUNCE:
+		tcpm_set_state(port, SRC_ATTACHED, PD_T_PD_DEBOUNCE);
+		break;
+	case SNK_TRYWAIT:
+		tcpm_set_cc(port, TYPEC_CC_RD);
+		tcpm_set_state(port, SNK_TRYWAIT_DEBOUNCE, PD_T_CC_DEBOUNCE);
+		break;
+	case SNK_TRYWAIT_DEBOUNCE:
+		if (port->vbus_present) {
+			tcpm_set_state(port, SNK_ATTACHED, 0);
+			break;
+		}
+		if (tcpm_port_is_disconnected(port)) {
+			tcpm_set_state(port, SNK_UNATTACHED,
+				       PD_T_PD_DEBOUNCE);
+			break;
+		}
+		if (tcpm_port_is_source(port))
+			tcpm_set_state(port, SRC_ATTACHED, 0);
+		/* XXX Are we supposed to stay in this state ? */
+		break;
+	case SNK_TRYWAIT_VBUS:
+		tcpm_set_state(port, SNK_ATTACHED, PD_T_CC_DEBOUNCE);
+		break;
+
+	case SNK_ATTACHED:
+		ret = tcpm_snk_attach(port);
+		if (ret < 0)
+			tcpm_set_state(port, SNK_UNATTACHED, 0);
+		else
+			tcpm_set_state(port, SNK_STARTUP, 0);
+		break;
+	case SNK_STARTUP:
+		/* XXX: callback into infrastructure */
+		typec_set_pwr_opmode(port->typec_port, TYPEC_PWR_MODE_USB);
+		port->pwr_opmode = TYPEC_PWR_MODE_USB;
+		port->message_id = 0;
+		port->explicit_contract = false;
+		tcpm_set_state(port, SNK_DISCOVERY, 0);
+		break;
+	case SNK_DISCOVERY:
+		if (port->vbus_present) {
+			tcpm_set_current_limit(port,
+					       tcpm_get_current_limit(port),
+					       5000);
+			tcpm_set_charge(port, true);
+			tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0);
+			break;
+		}
+		/*
+		 * For DRP, timeouts differ. Also, handling is supposed to be
+		 * different and much more complex (dead battery detection;
+		 * see USB power delivery specification, section 8.3.3.6.1.5.1).
+		 */
+		tcpm_set_state(port, hard_reset_state(port),
+			       port->typec_caps.type == TYPEC_PORT_DRP ?
+					PD_T_DB_DETECT : PD_T_NO_RESPONSE);
+		break;
+	case SNK_DISCOVERY_DEBOUNCE:
+		tcpm_set_state(port, SNK_DISCOVERY_DEBOUNCE_DONE,
+			       PD_T_CC_DEBOUNCE);
+		break;
+	case SNK_DISCOVERY_DEBOUNCE_DONE:
+		if (!tcpm_port_is_disconnected(port) &&
+		    tcpm_port_is_sink(port) &&
+		    time_is_after_jiffies(port->delayed_runtime)) {
+			tcpm_set_state(port, SNK_DISCOVERY,
+				       port->delayed_runtime - jiffies);
+			break;
+		}
+		tcpm_set_state(port, unattached_state(port), 0);
+		break;
+	case SNK_WAIT_CAPABILITIES:
+		ret = port->tcpc->set_pd_rx(port->tcpc, true);
+		if (ret < 0) {
+			tcpm_set_state(port, SNK_READY, 0);
+			break;
+		}
+		/*
+		 * If VBUS has never been low, and we time out waiting
+		 * for source cap, try a soft reset first, in case we
+		 * were already in a stable contract before this boot.
+		 * Do this only once.
+		 */
+		if (port->vbus_never_low) {
+			port->vbus_never_low = false;
+			tcpm_set_state(port, SOFT_RESET_SEND,
+				       PD_T_SINK_WAIT_CAP);
+		} else {
+			tcpm_set_state(port, hard_reset_state(port),
+				       PD_T_SINK_WAIT_CAP);
+		}
+		break;
+	case SNK_NEGOTIATE_CAPABILITIES:
+		port->pd_capable = true;
+		port->hard_reset_count = 0;
+		ret = tcpm_pd_send_request(port);
+		if (ret < 0) {
+			/* Let the Source send capabilities again. */
+			tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0);
+		} else {
+			tcpm_set_state_cond(port, hard_reset_state(port),
+					    PD_T_SENDER_RESPONSE);
+		}
+		break;
+	case SNK_TRANSITION_SINK:
+	case SNK_TRANSITION_SINK_VBUS:
+		tcpm_set_state(port, hard_reset_state(port),
+			       PD_T_PS_TRANSITION);
+		break;
+	case SNK_READY:
+		port->try_snk_count = 0;
+		port->explicit_contract = true;
+		typec_set_pwr_opmode(port->typec_port, TYPEC_PWR_MODE_PD);
+		port->pwr_opmode = TYPEC_PWR_MODE_PD;
+
+		tcpm_typec_connect(port);
+
+		tcpm_check_send_discover(port);
+		break;
+
+	/* Accessory states */
+	case ACC_UNATTACHED:
+		tcpm_acc_detach(port);
+		tcpm_set_state(port, SRC_UNATTACHED, 0);
+		break;
+	case DEBUG_ACC_ATTACHED:
+	case AUDIO_ACC_ATTACHED:
+		ret = tcpm_acc_attach(port);
+		if (ret < 0)
+			tcpm_set_state(port, ACC_UNATTACHED, 0);
+		break;
+	case AUDIO_ACC_DEBOUNCE:
+		tcpm_set_state(port, ACC_UNATTACHED, PD_T_CC_DEBOUNCE);
+		break;
+
+	/* Hard_Reset states */
+	case HARD_RESET_SEND:
+		tcpm_pd_transmit(port, TCPC_TX_HARD_RESET, NULL);
+		tcpm_set_state(port, HARD_RESET_START, 0);
+		break;
+	case HARD_RESET_START:
+		port->hard_reset_count++;
+		port->tcpc->set_pd_rx(port->tcpc, false);
+		tcpm_unregister_altmodes(port);
+		port->send_discover = true;
+		if (port->pwr_role == TYPEC_SOURCE)
+			tcpm_set_state(port, SRC_HARD_RESET_VBUS_OFF,
+				       PD_T_PS_HARD_RESET);
+		else
+			tcpm_set_state(port, SNK_HARD_RESET_SINK_OFF, 0);
+		break;
+	case SRC_HARD_RESET_VBUS_OFF:
+		tcpm_set_vconn(port, true);
+		tcpm_set_vbus(port, false);
+		tcpm_set_roles(port, false, TYPEC_SOURCE, TYPEC_HOST);
+		tcpm_set_state(port, SRC_HARD_RESET_VBUS_ON, PD_T_SRC_RECOVER);
+		break;
+	case SRC_HARD_RESET_VBUS_ON:
+		tcpm_set_vbus(port, true);
+		port->tcpc->set_pd_rx(port->tcpc, true);
+		tcpm_set_attached_state(port, true);
+		tcpm_set_state(port, SRC_UNATTACHED, PD_T_PS_SOURCE_ON);
+		break;
+	case SNK_HARD_RESET_SINK_OFF:
+		tcpm_set_vconn(port, false);
+		tcpm_set_charge(port, false);
+		tcpm_set_roles(port, false, TYPEC_SINK, TYPEC_DEVICE);
+		/*
+		 * VBUS may or may not toggle, depending on the adapter.
+		 * If it doesn't toggle, transition to SNK_HARD_RESET_SINK_ON
+		 * directly after timeout.
+		 */
+		tcpm_set_state(port, SNK_HARD_RESET_SINK_ON, PD_T_SAFE_0V);
+		break;
+	case SNK_HARD_RESET_WAIT_VBUS:
+		/* Assume we're disconnected if VBUS doesn't come back. */
+		tcpm_set_state(port, SNK_UNATTACHED,
+			       PD_T_SRC_RECOVER_MAX + PD_T_SRC_TURN_ON);
+		break;
+	case SNK_HARD_RESET_SINK_ON:
+		/* Note: There is no guarantee that VBUS is on in this state */
+		/*
+		 * XXX:
+		 * The specification suggests that dual mode ports in sink
+		 * mode should transition to state PE_SRC_Transition_to_default.
+		 * See USB power delivery specification chapter 8.3.3.6.1.3.
+		 * This would mean to to
+		 * - turn off VCONN, reset power supply
+		 * - request hardware reset
+		 * - turn on VCONN
+		 * - Transition to state PE_Src_Startup
+		 * SNK only ports shall transition to state Snk_Startup
+		 * (see chapter 8.3.3.3.8).
+		 * Similar, dual-mode ports in source mode should transition
+		 * to PE_SNK_Transition_to_default.
+		 */
+		tcpm_set_attached_state(port, true);
+		tcpm_set_state(port, SNK_STARTUP, 0);
+		break;
+
+	/* Soft_Reset states */
+	case SOFT_RESET:
+		port->message_id = 0;
+		tcpm_pd_send_control(port, PD_CTRL_ACCEPT);
+		if (port->pwr_role == TYPEC_SOURCE)
+			tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0);
+		else
+			tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0);
+		break;
+	case SOFT_RESET_SEND:
+		port->message_id = 0;
+		if (tcpm_pd_send_control(port, PD_CTRL_SOFT_RESET))
+			tcpm_set_state_cond(port, hard_reset_state(port), 0);
+		else
+			tcpm_set_state_cond(port, hard_reset_state(port),
+					    PD_T_SENDER_RESPONSE);
+		break;
+
+	/* DR_Swap states */
+	case DR_SWAP_SEND:
+		tcpm_pd_send_control(port, PD_CTRL_DR_SWAP);
+		tcpm_set_state_cond(port, DR_SWAP_SEND_TIMEOUT,
+				    PD_T_SENDER_RESPONSE);
+		break;
+	case DR_SWAP_ACCEPT:
+		tcpm_pd_send_control(port, PD_CTRL_ACCEPT);
+		tcpm_set_state_cond(port, DR_SWAP_CHANGE_DR, 0);
+		break;
+	case DR_SWAP_SEND_TIMEOUT:
+		tcpm_swap_complete(port, -ETIMEDOUT);
+		tcpm_set_state(port, ready_state(port), 0);
+		break;
+	case DR_SWAP_CHANGE_DR:
+		if (port->data_role == TYPEC_HOST) {
+			tcpm_unregister_altmodes(port);
+			tcpm_set_roles(port, true, port->pwr_role,
+				       TYPEC_DEVICE);
+		} else {
+			tcpm_set_roles(port, true, port->pwr_role,
+				       TYPEC_HOST);
+			port->send_discover = true;
+		}
+		tcpm_swap_complete(port, 0);
+		tcpm_set_state(port, ready_state(port), 0);
+		break;
+
+	/* PR_Swap states */
+	case PR_SWAP_ACCEPT:
+		tcpm_pd_send_control(port, PD_CTRL_ACCEPT);
+		tcpm_set_state(port, PR_SWAP_START, 0);
+		break;
+	case PR_SWAP_SEND:
+		tcpm_pd_send_control(port, PD_CTRL_PR_SWAP);
+		tcpm_set_state_cond(port, PR_SWAP_SEND_TIMEOUT,
+				    PD_T_SENDER_RESPONSE);
+		break;
+	case PR_SWAP_SEND_TIMEOUT:
+		tcpm_swap_complete(port, -ETIMEDOUT);
+		tcpm_set_state(port, ready_state(port), 0);
+		break;
+	case PR_SWAP_START:
+		if (port->pwr_role == TYPEC_SOURCE)
+			tcpm_set_state(port, PR_SWAP_SRC_SNK_TRANSITION_OFF,
+				       PD_T_SRC_TRANSITION);
+		else
+			tcpm_set_state(port, PR_SWAP_SNK_SRC_SINK_OFF, 0);
+		break;
+	case PR_SWAP_SRC_SNK_TRANSITION_OFF:
+		tcpm_set_vbus(port, false);
+		port->explicit_contract = false;
+		tcpm_set_state(port, PR_SWAP_SRC_SNK_SOURCE_OFF,
+			       PD_T_PS_SOURCE_OFF);
+		break;
+	case PR_SWAP_SRC_SNK_SOURCE_OFF:
+		tcpm_set_cc(port, TYPEC_CC_RD);
+		if (tcpm_pd_send_control(port, PD_CTRL_PS_RDY)) {
+			tcpm_set_state(port, ERROR_RECOVERY, 0);
+			break;
+		}
+		tcpm_set_state_cond(port, SNK_UNATTACHED, PD_T_PS_SOURCE_ON);
+		break;
+	case PR_SWAP_SRC_SNK_SINK_ON:
+		tcpm_set_pwr_role(port, TYPEC_SINK);
+		tcpm_swap_complete(port, 0);
+		tcpm_set_state(port, SNK_STARTUP, 0);
+		break;
+	case PR_SWAP_SNK_SRC_SINK_OFF:
+		tcpm_set_charge(port, false);
+		tcpm_set_state(port, hard_reset_state(port),
+			       PD_T_PS_SOURCE_OFF);
+		break;
+	case PR_SWAP_SNK_SRC_SOURCE_ON:
+		tcpm_set_cc(port, tcpm_rp_cc(port));
+		tcpm_set_vbus(port, true);
+		tcpm_pd_send_control(port, PD_CTRL_PS_RDY);
+		tcpm_set_pwr_role(port, TYPEC_SOURCE);
+		tcpm_swap_complete(port, 0);
+		tcpm_set_state(port, SRC_STARTUP, 0);
+		break;
+
+	case VCONN_SWAP_ACCEPT:
+		tcpm_pd_send_control(port, PD_CTRL_ACCEPT);
+		tcpm_set_state(port, VCONN_SWAP_START, 0);
+		break;
+	case VCONN_SWAP_SEND:
+		tcpm_pd_send_control(port, PD_CTRL_VCONN_SWAP);
+		tcpm_set_state(port, VCONN_SWAP_SEND_TIMEOUT,
+			       PD_T_SENDER_RESPONSE);
+		break;
+	case VCONN_SWAP_SEND_TIMEOUT:
+		tcpm_swap_complete(port, -ETIMEDOUT);
+		tcpm_set_state(port, ready_state(port), 0);
+		break;
+	case VCONN_SWAP_START:
+		if (port->vconn_role == TYPEC_SOURCE)
+			tcpm_set_state(port, VCONN_SWAP_WAIT_FOR_VCONN, 0);
+		else
+			tcpm_set_state(port, VCONN_SWAP_TURN_ON_VCONN, 0);
+		break;
+	case VCONN_SWAP_WAIT_FOR_VCONN:
+		tcpm_set_state(port, hard_reset_state(port),
+			       PD_T_VCONN_SOURCE_ON);
+		break;
+	case VCONN_SWAP_TURN_ON_VCONN:
+		tcpm_set_vconn(port, true);
+		tcpm_pd_send_control(port, PD_CTRL_PS_RDY);
+		tcpm_swap_complete(port, 0);
+		tcpm_set_state(port, ready_state(port), 0);
+		break;
+	case VCONN_SWAP_TURN_OFF_VCONN:
+		tcpm_set_vconn(port, false);
+		tcpm_swap_complete(port, 0);
+		tcpm_set_state(port, ready_state(port), 0);
+		break;
+
+	case DR_SWAP_CANCEL:
+	case PR_SWAP_CANCEL:
+	case VCONN_SWAP_CANCEL:
+		tcpm_swap_complete(port, port->swap_status);
+		if (port->pwr_role == TYPEC_SOURCE)
+			tcpm_set_state(port, SRC_READY, 0);
+		else
+			tcpm_set_state(port, SNK_READY, 0);
+		break;
+
+	case BIST_RX:
+		switch (BDO_MODE_MASK(port->bist_request)) {
+		case BDO_MODE_CARRIER2:
+			tcpm_pd_transmit(port, TCPC_TX_BIST_MODE_2, NULL);
+			break;
+		default:
+			break;
+		}
+		/* Always switch to unattached state */
+		tcpm_set_state(port, unattached_state(port), 0);
+		break;
+	case ERROR_RECOVERY:
+		tcpm_swap_complete(port, -EPROTO);
+		tcpm_reset_port(port);
+
+		tcpm_set_cc(port, TYPEC_CC_OPEN);
+		tcpm_set_state(port, ERROR_RECOVERY_WAIT_OFF,
+			       PD_T_ERROR_RECOVERY);
+		break;
+	case ERROR_RECOVERY_WAIT_OFF:
+		tcpm_set_state(port,
+			       tcpm_default_state(port),
+			       port->vbus_present ? PD_T_PS_SOURCE_OFF : 0);
+		break;
+	default:
+		WARN(1, "Unexpected port state %d\n", port->state);
+		break;
+	}
+}
+
+static void tcpm_state_machine_work(struct work_struct *work)
+{
+	struct tcpm_port *port = container_of(work, struct tcpm_port,
+					      state_machine.work);
+	enum tcpm_state prev_state;
+
+	mutex_lock(&port->lock);
+	port->state_machine_running = true;
+
+	if (port->queued_message && tcpm_send_queued_message(port))
+		goto done;
+
+	/* If we were queued due to a delayed state change, update it now */
+	if (port->delayed_state) {
+		tcpm_log(port, "state change %s -> %s [delayed %ld ms]",
+			 tcpm_states[port->state],
+			 tcpm_states[port->delayed_state], port->delay_ms);
+		port->prev_state = port->state;
+		port->state = port->delayed_state;
+		port->delayed_state = INVALID_STATE;
+	}
+
+	/*
+	 * Continue running as long as we have (non-delayed) state changes
+	 * to make.
+	 */
+	do {
+		prev_state = port->state;
+		run_state_machine(port);
+		if (port->queued_message)
+			tcpm_send_queued_message(port);
+	} while (port->state != prev_state && !port->delayed_state);
+
+done:
+	port->state_machine_running = false;
+	mutex_unlock(&port->lock);
+}
+
+static void _tcpm_cc_change(struct tcpm_port *port, enum typec_cc_status cc1,
+			    enum typec_cc_status cc2)
+{
+	enum typec_cc_status old_cc1, old_cc2;
+	enum tcpm_state new_state;
+
+	old_cc1 = port->cc1;
+	old_cc2 = port->cc2;
+	port->cc1 = cc1;
+	port->cc2 = cc2;
+
+	tcpm_log_force(port,
+		       "CC1: %u -> %u, CC2: %u -> %u [state %s, polarity %d, %s]",
+		       old_cc1, cc1, old_cc2, cc2, tcpm_states[port->state],
+		       port->polarity,
+		       tcpm_port_is_disconnected(port) ? "disconnected"
+						       : "connected");
+
+	switch (port->state) {
+	case DRP_TOGGLING:
+		if (tcpm_port_is_debug(port) || tcpm_port_is_audio(port) ||
+		    tcpm_port_is_source(port))
+			tcpm_set_state(port, SRC_ATTACH_WAIT, 0);
+		else if (tcpm_port_is_sink(port))
+			tcpm_set_state(port, SNK_ATTACH_WAIT, 0);
+		break;
+	case SRC_UNATTACHED:
+	case ACC_UNATTACHED:
+		if (tcpm_port_is_debug(port) || tcpm_port_is_audio(port) ||
+		    tcpm_port_is_source(port))
+			tcpm_set_state(port, SRC_ATTACH_WAIT, 0);
+		break;
+	case SRC_ATTACH_WAIT:
+		if (tcpm_port_is_disconnected(port) ||
+		    tcpm_port_is_audio_detached(port))
+			tcpm_set_state(port, SRC_UNATTACHED, 0);
+		else if (cc1 != old_cc1 || cc2 != old_cc2)
+			tcpm_set_state(port, SRC_ATTACH_WAIT, 0);
+		break;
+	case SRC_ATTACHED:
+		if (tcpm_port_is_disconnected(port))
+			tcpm_set_state(port, SRC_UNATTACHED, 0);
+		break;
+
+	case SNK_UNATTACHED:
+		if (tcpm_port_is_sink(port))
+			tcpm_set_state(port, SNK_ATTACH_WAIT, 0);
+		break;
+	case SNK_ATTACH_WAIT:
+		if ((port->cc1 == TYPEC_CC_OPEN &&
+		     port->cc2 != TYPEC_CC_OPEN) ||
+		    (port->cc1 != TYPEC_CC_OPEN &&
+		     port->cc2 == TYPEC_CC_OPEN))
+			new_state = SNK_DEBOUNCED;
+		else if (tcpm_port_is_disconnected(port))
+			new_state = SNK_UNATTACHED;
+		else
+			break;
+		if (new_state != port->delayed_state)
+			tcpm_set_state(port, SNK_ATTACH_WAIT, 0);
+		break;
+	case SNK_DEBOUNCED:
+		if (tcpm_port_is_disconnected(port))
+			new_state = SNK_UNATTACHED;
+		else if (port->vbus_present)
+			new_state = tcpm_try_src(port) ? SRC_TRY : SNK_ATTACHED;
+		else
+			new_state = SNK_UNATTACHED;
+		if (new_state != port->delayed_state)
+			tcpm_set_state(port, SNK_DEBOUNCED, 0);
+		break;
+	case SNK_READY:
+		if (tcpm_port_is_disconnected(port))
+			tcpm_set_state(port, unattached_state(port), 0);
+		else if (!port->pd_capable &&
+			 (cc1 != old_cc1 || cc2 != old_cc2))
+			tcpm_set_current_limit(port,
+					       tcpm_get_current_limit(port),
+					       5000);
+		break;
+
+	case AUDIO_ACC_ATTACHED:
+		if (cc1 == TYPEC_CC_OPEN || cc2 == TYPEC_CC_OPEN)
+			tcpm_set_state(port, AUDIO_ACC_DEBOUNCE, 0);
+		break;
+	case AUDIO_ACC_DEBOUNCE:
+		if (tcpm_port_is_audio(port))
+			tcpm_set_state(port, AUDIO_ACC_ATTACHED, 0);
+		break;
+
+	case DEBUG_ACC_ATTACHED:
+		if (cc1 == TYPEC_CC_OPEN || cc2 == TYPEC_CC_OPEN)
+			tcpm_set_state(port, ACC_UNATTACHED, 0);
+		break;
+
+	case SNK_TRY:
+		/* Do nothing, waiting for timeout */
+		break;
+
+	case SNK_DISCOVERY:
+		/* CC line is unstable, wait for debounce */
+		if (tcpm_port_is_disconnected(port))
+			tcpm_set_state(port, SNK_DISCOVERY_DEBOUNCE, 0);
+		break;
+	case SNK_DISCOVERY_DEBOUNCE:
+		break;
+
+	case SRC_TRYWAIT:
+		/* Hand over to state machine if needed */
+		if (!port->vbus_present && tcpm_port_is_source(port))
+			new_state = SRC_ATTACHED;
+		else
+			new_state = SRC_TRYWAIT_UNATTACHED;
+
+		if (new_state != port->delayed_state)
+			tcpm_set_state(port, SRC_TRYWAIT, 0);
+		break;
+	case SNK_TRY_WAIT:
+		if (port->vbus_present && tcpm_port_is_sink(port)) {
+			tcpm_set_state(port, SNK_ATTACHED, 0);
+			break;
+		}
+		if (!tcpm_port_is_sink(port))
+			new_state = SRC_TRYWAIT;
+		else
+			new_state = SRC_TRYWAIT_UNATTACHED;
+
+		if (new_state != port->delayed_state)
+			tcpm_set_state(port, SNK_TRY_WAIT, 0);
+		break;
+
+	case SRC_TRY:
+		tcpm_set_state(port, SRC_TRY_DEBOUNCE, 0);
+		break;
+	case SRC_TRY_DEBOUNCE:
+		tcpm_set_state(port, SRC_TRY, 0);
+		break;
+	case SNK_TRYWAIT_DEBOUNCE:
+		if (port->vbus_present) {
+			tcpm_set_state(port, SNK_ATTACHED, 0);
+			break;
+		}
+		if (tcpm_port_is_source(port)) {
+			tcpm_set_state(port, SRC_ATTACHED, 0);
+			break;
+		}
+		if (tcpm_port_is_disconnected(port) &&
+		    port->delayed_state != SNK_UNATTACHED)
+			tcpm_set_state(port, SNK_TRYWAIT_DEBOUNCE, 0);
+		break;
+
+	case PR_SWAP_SNK_SRC_SINK_OFF:
+	case PR_SWAP_SRC_SNK_TRANSITION_OFF:
+	case PR_SWAP_SRC_SNK_SOURCE_OFF:
+		/*
+		 * CC state change is expected here; we just turned off power.
+		 * Ignore it.
+		 */
+		break;
+
+	default:
+		if (tcpm_port_is_disconnected(port))
+			tcpm_set_state(port, unattached_state(port), 0);
+		break;
+	}
+}
+
+static void _tcpm_pd_vbus_on(struct tcpm_port *port)
+{
+	enum tcpm_state new_state;
+
+	tcpm_log_force(port, "VBUS on");
+	port->vbus_present = true;
+	switch (port->state) {
+	case SNK_TRANSITION_SINK_VBUS:
+		tcpm_set_state(port, SNK_READY, 0);
+		break;
+	case SNK_DISCOVERY:
+		tcpm_set_state(port, SNK_DISCOVERY, 0);
+		break;
+
+	case SNK_DEBOUNCED:
+		tcpm_set_state(port, tcpm_try_src(port) ? SRC_TRY
+							: SNK_ATTACHED,
+				       0);
+		break;
+	case SNK_HARD_RESET_WAIT_VBUS:
+		tcpm_set_state(port, SNK_HARD_RESET_SINK_ON, 0);
+		break;
+	case SRC_ATTACHED:
+		tcpm_set_state(port, SRC_STARTUP, 0);
+		break;
+	case SRC_HARD_RESET_VBUS_ON:
+		tcpm_set_state(port, SRC_STARTUP, 0);
+		break;
+
+	case SNK_TRY:
+		/* Do nothing, waiting for timeout */
+		break;
+	case SRC_TRYWAIT:
+		/* Hand over to state machine if needed */
+		if (port->delayed_state != SRC_TRYWAIT_UNATTACHED)
+			tcpm_set_state(port, SRC_TRYWAIT, 0);
+		break;
+	case SNK_TRY_WAIT:
+		if (tcpm_port_is_sink(port)) {
+			tcpm_set_state(port, SNK_ATTACHED, 0);
+			break;
+		}
+		if (!tcpm_port_is_sink(port))
+			new_state = SRC_TRYWAIT;
+		else
+			new_state = SRC_TRYWAIT_UNATTACHED;
+
+		if (new_state != port->delayed_state)
+			tcpm_set_state(port, SNK_TRY_WAIT, 0);
+		break;
+	case SNK_TRYWAIT:
+		tcpm_set_state(port, SNK_TRYWAIT_VBUS, 0);
+		break;
+
+	default:
+		break;
+	}
+}
+
+static void _tcpm_pd_vbus_off(struct tcpm_port *port)
+{
+	enum tcpm_state new_state;
+
+	tcpm_log_force(port, "VBUS off");
+	port->vbus_present = false;
+	port->vbus_never_low = false;
+	switch (port->state) {
+	case SNK_HARD_RESET_SINK_OFF:
+		tcpm_set_state(port, SNK_HARD_RESET_WAIT_VBUS, 0);
+		break;
+	case SRC_HARD_RESET_VBUS_OFF:
+		tcpm_set_state(port, SRC_HARD_RESET_VBUS_ON, 0);
+		break;
+	case HARD_RESET_SEND:
+		break;
+
+	case SNK_TRY:
+		/* Do nothing, waiting for timeout */
+		break;
+	case SRC_TRYWAIT:
+		/* Hand over to state machine if needed */
+		if (tcpm_port_is_source(port))
+			new_state = SRC_ATTACHED;
+		else
+			new_state = SRC_TRYWAIT_UNATTACHED;
+		if (new_state != port->delayed_state)
+			tcpm_set_state(port, SRC_TRYWAIT, 0);
+		break;
+	case SNK_TRY_WAIT:
+		if (!tcpm_port_is_sink(port))
+			new_state = SRC_TRYWAIT;
+		else
+			new_state = SRC_TRYWAIT_UNATTACHED;
+
+		if (new_state != port->delayed_state)
+			tcpm_set_state(port, SNK_TRY_WAIT, 0);
+		break;
+	case SNK_TRYWAIT_VBUS:
+		tcpm_set_state(port, SNK_TRYWAIT, 0);
+		break;
+
+	case SNK_ATTACH_WAIT:
+		tcpm_set_state(port, SNK_UNATTACHED, 0);
+		break;
+
+	case SNK_NEGOTIATE_CAPABILITIES:
+		break;
+
+	case PR_SWAP_SRC_SNK_TRANSITION_OFF:
+		tcpm_set_state(port, PR_SWAP_SRC_SNK_SOURCE_OFF, 0);
+		break;
+
+	case PR_SWAP_SNK_SRC_SINK_OFF:
+		/* Do nothing, expected */
+		break;
+
+	case ERROR_RECOVERY_WAIT_OFF:
+		tcpm_set_state(port,
+			       port->pwr_role == TYPEC_SOURCE ?
+					SRC_UNATTACHED : SNK_UNATTACHED,
+			       0);
+		break;
+
+	default:
+		if (port->pwr_role == TYPEC_SINK &&
+		    port->attached)
+			tcpm_set_state(port, SNK_UNATTACHED, 0);
+		break;
+	}
+}
+
+static void _tcpm_pd_hard_reset(struct tcpm_port *port)
+{
+	tcpm_log_force(port, "Received hard reset");
+	/*
+	 * If we keep receiving hard reset requests, executing the hard reset
+	 * must have failed. Revert to error recovery if that happens.
+	 */
+	tcpm_set_state(port,
+		       port->hard_reset_count < PD_N_HARD_RESET_COUNT ?
+				HARD_RESET_START : ERROR_RECOVERY,
+		       0);
+}
+
+static void tcpm_pd_event_handler(struct work_struct *work)
+{
+	struct tcpm_port *port = container_of(work, struct tcpm_port,
+					      event_work);
+	u32 events;
+
+	mutex_lock(&port->lock);
+
+	spin_lock(&port->pd_event_lock);
+	while (port->pd_events) {
+		events = port->pd_events;
+		port->pd_events = 0;
+		spin_unlock(&port->pd_event_lock);
+		if (events & TCPM_RESET_EVENT)
+			_tcpm_pd_hard_reset(port);
+		if (events & TCPM_VBUS_EVENT) {
+			bool vbus;
+
+			vbus = port->tcpc->get_vbus(port->tcpc);
+			if (vbus)
+				_tcpm_pd_vbus_on(port);
+			else
+				_tcpm_pd_vbus_off(port);
+		}
+		if (events & TCPM_CC_EVENT) {
+			enum typec_cc_status cc1, cc2;
+
+			if (port->tcpc->get_cc(port->tcpc, &cc1, &cc2) == 0)
+				_tcpm_cc_change(port, cc1, cc2);
+		}
+		spin_lock(&port->pd_event_lock);
+	}
+	spin_unlock(&port->pd_event_lock);
+	mutex_unlock(&port->lock);
+}
+
+void tcpm_cc_change(struct tcpm_port *port)
+{
+	spin_lock(&port->pd_event_lock);
+	port->pd_events |= TCPM_CC_EVENT;
+	spin_unlock(&port->pd_event_lock);
+	queue_work(port->wq, &port->event_work);
+}
+EXPORT_SYMBOL_GPL(tcpm_cc_change);
+
+void tcpm_vbus_change(struct tcpm_port *port)
+{
+	spin_lock(&port->pd_event_lock);
+	port->pd_events |= TCPM_VBUS_EVENT;
+	spin_unlock(&port->pd_event_lock);
+	queue_work(port->wq, &port->event_work);
+}
+EXPORT_SYMBOL_GPL(tcpm_vbus_change);
+
+void tcpm_pd_hard_reset(struct tcpm_port *port)
+{
+	spin_lock(&port->pd_event_lock);
+	port->pd_events = TCPM_RESET_EVENT;
+	spin_unlock(&port->pd_event_lock);
+	queue_work(port->wq, &port->event_work);
+}
+EXPORT_SYMBOL_GPL(tcpm_pd_hard_reset);
+
+static int tcpm_dr_set(const struct typec_capability *cap,
+		       enum typec_data_role data)
+{
+	struct tcpm_port *port = typec_cap_to_tcpm(cap);
+	int ret;
+
+	mutex_lock(&port->swap_lock);
+	mutex_lock(&port->lock);
+
+	if (port->typec_caps.type != TYPEC_PORT_DRP || !port->pd_capable) {
+		ret = -EINVAL;
+		goto port_unlock;
+	}
+	if (port->state != SRC_READY && port->state != SNK_READY) {
+		ret = -EAGAIN;
+		goto port_unlock;
+	}
+
+	if (port->data_role == data) {
+		ret = 0;
+		goto port_unlock;
+	}
+
+	/*
+	 * XXX
+	 * 6.3.9: If an alternate mode is active, a request to swap
+	 * alternate modes shall trigger a port reset.
+	 * Reject data role swap request in this case.
+	 */
+
+	port->swap_status = 0;
+	port->swap_pending = true;
+	reinit_completion(&port->swap_complete);
+	tcpm_set_state(port, DR_SWAP_SEND, 0);
+	mutex_unlock(&port->lock);
+
+	wait_for_completion(&port->swap_complete);
+
+	ret = port->swap_status;
+	goto swap_unlock;
+
+port_unlock:
+	mutex_unlock(&port->lock);
+swap_unlock:
+	mutex_unlock(&port->swap_lock);
+	return ret;
+}
+
+static int tcpm_pr_set(const struct typec_capability *cap,
+		       enum typec_role role)
+{
+	struct tcpm_port *port = typec_cap_to_tcpm(cap);
+	int ret;
+
+	mutex_lock(&port->swap_lock);
+	mutex_lock(&port->lock);
+
+	if (port->typec_caps.type != TYPEC_PORT_DRP) {
+		ret = -EINVAL;
+		goto port_unlock;
+	}
+	if (port->state != SRC_READY && port->state != SNK_READY) {
+		ret = -EAGAIN;
+		goto port_unlock;
+	}
+
+	if (role == port->pwr_role) {
+		ret = 0;
+		goto port_unlock;
+	}
+
+	if (!port->pd_capable) {
+		/*
+		 * If the partner is not PD capable, reset the port to
+		 * trigger a role change. This can only work if a preferred
+		 * role is configured, and if it matches the requested role.
+		 */
+		if (port->try_role == TYPEC_NO_PREFERRED_ROLE ||
+		    port->try_role == port->pwr_role) {
+			ret = -EINVAL;
+			goto port_unlock;
+		}
+		tcpm_set_state(port, HARD_RESET_SEND, 0);
+		ret = 0;
+		goto port_unlock;
+	}
+
+	port->swap_status = 0;
+	port->swap_pending = true;
+	reinit_completion(&port->swap_complete);
+	tcpm_set_state(port, PR_SWAP_SEND, 0);
+	mutex_unlock(&port->lock);
+
+	wait_for_completion(&port->swap_complete);
+
+	ret = port->swap_status;
+	goto swap_unlock;
+
+port_unlock:
+	mutex_unlock(&port->lock);
+swap_unlock:
+	mutex_unlock(&port->swap_lock);
+	return ret;
+}
+
+static int tcpm_vconn_set(const struct typec_capability *cap,
+			  enum typec_role role)
+{
+	struct tcpm_port *port = typec_cap_to_tcpm(cap);
+	int ret;
+
+	mutex_lock(&port->swap_lock);
+	mutex_lock(&port->lock);
+
+	if (port->state != SRC_READY && port->state != SNK_READY) {
+		ret = -EAGAIN;
+		goto port_unlock;
+	}
+
+	if (role == port->vconn_role) {
+		ret = 0;
+		goto port_unlock;
+	}
+
+	port->swap_status = 0;
+	port->swap_pending = true;
+	reinit_completion(&port->swap_complete);
+	tcpm_set_state(port, VCONN_SWAP_SEND, 0);
+	mutex_unlock(&port->lock);
+
+	wait_for_completion(&port->swap_complete);
+
+	ret = port->swap_status;
+	goto swap_unlock;
+
+port_unlock:
+	mutex_unlock(&port->lock);
+swap_unlock:
+	mutex_unlock(&port->swap_lock);
+	return ret;
+}
+
+static int tcpm_try_role(const struct typec_capability *cap, int role)
+{
+	struct tcpm_port *port = typec_cap_to_tcpm(cap);
+	struct tcpc_dev	*tcpc = port->tcpc;
+	int ret = 0;
+
+	mutex_lock(&port->lock);
+	if (tcpc->try_role)
+		ret = tcpc->try_role(tcpc, role);
+	if (!ret && !tcpc->config->try_role_hw)
+		port->try_role = role;
+	port->try_src_count = 0;
+	port->try_snk_count = 0;
+	mutex_unlock(&port->lock);
+
+	return ret;
+}
+
+static void tcpm_init(struct tcpm_port *port)
+{
+	enum typec_cc_status cc1, cc2;
+
+	port->tcpc->init(port->tcpc);
+
+	tcpm_reset_port(port);
+
+	/*
+	 * XXX
+	 * Should possibly wait for VBUS to settle if it was enabled locally
+	 * since tcpm_reset_port() will disable VBUS.
+	 */
+	port->vbus_present = port->tcpc->get_vbus(port->tcpc);
+	if (port->vbus_present)
+		port->vbus_never_low = true;
+
+	tcpm_set_state(port, tcpm_default_state(port), 0);
+
+	if (port->tcpc->get_cc(port->tcpc, &cc1, &cc2) == 0)
+		_tcpm_cc_change(port, cc1, cc2);
+
+	/*
+	 * Some adapters need a clean slate at startup, and won't recover
+	 * otherwise. So do not try to be fancy and force a clean disconnect.
+	 */
+	tcpm_set_state(port, ERROR_RECOVERY, 0);
+}
+
+void tcpm_tcpc_reset(struct tcpm_port *port)
+{
+	mutex_lock(&port->lock);
+	/* XXX: Maintain PD connection if possible? */
+	tcpm_init(port);
+	mutex_unlock(&port->lock);
+}
+EXPORT_SYMBOL_GPL(tcpm_tcpc_reset);
+
+static int tcpm_copy_pdos(u32 *dest_pdo, const u32 *src_pdo,
+			  unsigned int nr_pdo)
+{
+	unsigned int i;
+
+	if (nr_pdo > PDO_MAX_OBJECTS)
+		nr_pdo = PDO_MAX_OBJECTS;
+
+	for (i = 0; i < nr_pdo; i++)
+		dest_pdo[i] = src_pdo[i];
+
+	return nr_pdo;
+}
+
+void tcpm_update_source_capabilities(struct tcpm_port *port, const u32 *pdo,
+				     unsigned int nr_pdo)
+{
+	mutex_lock(&port->lock);
+	port->nr_src_pdo = tcpm_copy_pdos(port->src_pdo, pdo, nr_pdo);
+	switch (port->state) {
+	case SRC_UNATTACHED:
+	case SRC_ATTACH_WAIT:
+	case SRC_TRYWAIT:
+		tcpm_set_cc(port, tcpm_rp_cc(port));
+		break;
+	case SRC_SEND_CAPABILITIES:
+	case SRC_NEGOTIATE_CAPABILITIES:
+	case SRC_READY:
+	case SRC_WAIT_NEW_CAPABILITIES:
+		tcpm_set_cc(port, tcpm_rp_cc(port));
+		tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0);
+		break;
+	default:
+		break;
+	}
+	mutex_unlock(&port->lock);
+}
+EXPORT_SYMBOL_GPL(tcpm_update_source_capabilities);
+
+void tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
+				   unsigned int nr_pdo,
+				   unsigned int max_snk_mv,
+				   unsigned int max_snk_ma,
+				   unsigned int max_snk_mw,
+				   unsigned int operating_snk_mw)
+{
+	mutex_lock(&port->lock);
+	port->nr_snk_pdo = tcpm_copy_pdos(port->snk_pdo, pdo, nr_pdo);
+	port->max_snk_mv = max_snk_mv;
+	port->max_snk_ma = max_snk_ma;
+	port->max_snk_mw = max_snk_mw;
+	port->operating_snk_mw = operating_snk_mw;
+
+	switch (port->state) {
+	case SNK_NEGOTIATE_CAPABILITIES:
+	case SNK_READY:
+	case SNK_TRANSITION_SINK:
+	case SNK_TRANSITION_SINK_VBUS:
+		tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0);
+		break;
+	default:
+		break;
+	}
+	mutex_unlock(&port->lock);
+}
+EXPORT_SYMBOL_GPL(tcpm_update_sink_capabilities);
+
+struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
+{
+	struct tcpm_port *port;
+	int i, err;
+
+	if (!dev || !tcpc || !tcpc->config ||
+	    !tcpc->get_vbus || !tcpc->set_cc || !tcpc->get_cc ||
+	    !tcpc->set_polarity || !tcpc->set_vconn || !tcpc->set_vbus ||
+	    !tcpc->set_pd_rx || !tcpc->set_roles || !tcpc->pd_transmit)
+		return ERR_PTR(-EINVAL);
+
+	port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
+	if (!port)
+		return ERR_PTR(-ENOMEM);
+
+	port->dev = dev;
+	port->tcpc = tcpc;
+
+	mutex_init(&port->lock);
+	mutex_init(&port->swap_lock);
+
+	port->wq = create_singlethread_workqueue(dev_name(dev));
+	if (!port->wq)
+		return ERR_PTR(-ENOMEM);
+	INIT_DELAYED_WORK(&port->state_machine, tcpm_state_machine_work);
+	INIT_DELAYED_WORK(&port->vdm_state_machine, vdm_state_machine_work);
+	INIT_WORK(&port->event_work, tcpm_pd_event_handler);
+
+	spin_lock_init(&port->pd_event_lock);
+
+	init_completion(&port->tx_complete);
+	init_completion(&port->swap_complete);
+
+	port->nr_src_pdo = tcpm_copy_pdos(port->src_pdo, tcpc->config->src_pdo,
+					  tcpc->config->nr_src_pdo);
+	port->nr_snk_pdo = tcpm_copy_pdos(port->snk_pdo, tcpc->config->snk_pdo,
+					  tcpc->config->nr_snk_pdo);
+
+	port->max_snk_mv = tcpc->config->max_snk_mv;
+	port->max_snk_ma = tcpc->config->max_snk_ma;
+	port->max_snk_mw = tcpc->config->max_snk_mw;
+	port->operating_snk_mw = tcpc->config->operating_snk_mw;
+	if (!tcpc->config->try_role_hw)
+		port->try_role = tcpc->config->default_role;
+	else
+		port->try_role = TYPEC_NO_PREFERRED_ROLE;
+
+	port->typec_caps.prefer_role = tcpc->config->default_role;
+	port->typec_caps.type = tcpc->config->type;
+	port->typec_caps.revision = 0x0120;	/* Type-C spec release 1.2 */
+	port->typec_caps.pd_revision = 0x0200;	/* USB-PD spec release 2.0 */
+	port->typec_caps.dr_set = tcpm_dr_set;
+	port->typec_caps.pr_set = tcpm_pr_set;
+	port->typec_caps.vconn_set = tcpm_vconn_set;
+	port->typec_caps.try_role = tcpm_try_role;
+
+	port->partner_desc.identity = &port->partner_ident;
+
+	/*
+	 * TODO:
+	 *  - alt_modes, set_alt_mode
+	 *  - {debug,audio}_accessory
+	 */
+
+	port->typec_port = typec_register_port(port->dev, &port->typec_caps);
+	if (!port->typec_port) {
+		err = -ENOMEM;
+		goto out_destroy_wq;
+	}
+
+	if (tcpc->config->alt_modes) {
+		struct typec_altmode_desc *paltmode = tcpc->config->alt_modes;
+
+		i = 0;
+		while (paltmode->svid && i < ARRAY_SIZE(port->port_altmode)) {
+			port->port_altmode[i] =
+			  typec_port_register_altmode(port->typec_port,
+						      paltmode);
+			if (!port->port_altmode[i]) {
+				tcpm_log(port,
+					 "%s: failed to register port alternate mode 0x%x",
+					 dev_name(dev), paltmode->svid);
+				break;
+			}
+			i++;
+			paltmode++;
+		}
+	}
+
+	tcpm_debugfs_init(port);
+	mutex_lock(&port->lock);
+	tcpm_init(port);
+	mutex_unlock(&port->lock);
+
+	tcpm_log(port, "%s: registered", dev_name(dev));
+	return port;
+
+out_destroy_wq:
+	destroy_workqueue(port->wq);
+	return ERR_PTR(err);
+}
+EXPORT_SYMBOL_GPL(tcpm_register_port);
+
+void tcpm_unregister_port(struct tcpm_port *port)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(port->port_altmode); i++)
+		typec_unregister_altmode(port->port_altmode[i]);
+	typec_unregister_port(port->typec_port);
+	tcpm_debugfs_exit(port);
+	destroy_workqueue(port->wq);
+}
+EXPORT_SYMBOL_GPL(tcpm_unregister_port);
+
+MODULE_AUTHOR("Guenter Roeck <groeck@chromium.org>");
+MODULE_DESCRIPTION("USB Type-C Port Manager");
+MODULE_LICENSE("GPL");

+ 150 - 0
drivers/staging/typec/tcpm.h

@@ -0,0 +1,150 @@
+/*
+ * Copyright 2015-2017 Google, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __LINUX_USB_TCPM_H
+#define __LINUX_USB_TCPM_H
+
+#include <linux/bitops.h>
+#include <linux/usb/typec.h>
+#include "pd.h"
+
+enum typec_cc_status {
+	TYPEC_CC_OPEN,
+	TYPEC_CC_RA,
+	TYPEC_CC_RD,
+	TYPEC_CC_RP_DEF,
+	TYPEC_CC_RP_1_5,
+	TYPEC_CC_RP_3_0,
+};
+
+enum typec_cc_polarity {
+	TYPEC_POLARITY_CC1,
+	TYPEC_POLARITY_CC2,
+};
+
+/* Time to wait for TCPC to complete transmit */
+#define PD_T_TCPC_TX_TIMEOUT  100
+
+enum tcpm_transmit_status {
+	TCPC_TX_SUCCESS = 0,
+	TCPC_TX_DISCARDED = 1,
+	TCPC_TX_FAILED = 2,
+};
+
+enum tcpm_transmit_type {
+	TCPC_TX_SOP = 0,
+	TCPC_TX_SOP_PRIME = 1,
+	TCPC_TX_SOP_PRIME_PRIME = 2,
+	TCPC_TX_SOP_DEBUG_PRIME = 3,
+	TCPC_TX_SOP_DEBUG_PRIME_PRIME = 4,
+	TCPC_TX_HARD_RESET = 5,
+	TCPC_TX_CABLE_RESET = 6,
+	TCPC_TX_BIST_MODE_2 = 7
+};
+
+struct tcpc_config {
+	const u32 *src_pdo;
+	unsigned int nr_src_pdo;
+
+	const u32 *snk_pdo;
+	unsigned int nr_snk_pdo;
+
+	unsigned int max_snk_mv;
+	unsigned int max_snk_ma;
+	unsigned int max_snk_mw;
+	unsigned int operating_snk_mw;
+
+	enum typec_port_type type;
+	enum typec_role default_role;
+	bool try_role_hw;	/* try.{src,snk} implemented in hardware */
+
+	struct typec_altmode_desc *alt_modes;
+};
+
+enum tcpc_usb_switch {
+	TCPC_USB_SWITCH_CONNECT,
+	TCPC_USB_SWITCH_DISCONNECT,
+	TCPC_USB_SWITCH_RESTORE,	/* TODO FIXME */
+};
+
+/* Mux state attributes */
+#define TCPC_MUX_USB_ENABLED		BIT(0)	/* USB enabled */
+#define TCPC_MUX_DP_ENABLED		BIT(1)	/* DP enabled */
+#define TCPC_MUX_POLARITY_INVERTED	BIT(2)	/* Polarity inverted */
+
+/* Mux modes, decoded to attributes */
+enum tcpc_mux_mode {
+	TYPEC_MUX_NONE	= 0,				/* Open switch */
+	TYPEC_MUX_USB	= TCPC_MUX_USB_ENABLED,		/* USB only */
+	TYPEC_MUX_DP	= TCPC_MUX_DP_ENABLED,		/* DP only */
+	TYPEC_MUX_DOCK	= TCPC_MUX_USB_ENABLED |	/* Both USB and DP */
+			  TCPC_MUX_DP_ENABLED,
+};
+
+struct tcpc_mux_dev {
+	int (*set)(struct tcpc_mux_dev *dev, enum tcpc_mux_mode mux_mode,
+		   enum tcpc_usb_switch usb_config,
+		   enum typec_cc_polarity polarity);
+	bool dfp_only;
+	void *priv_data;
+};
+
+struct tcpc_dev {
+	const struct tcpc_config *config;
+
+	int (*init)(struct tcpc_dev *dev);
+	int (*get_vbus)(struct tcpc_dev *dev);
+	int (*set_cc)(struct tcpc_dev *dev, enum typec_cc_status cc);
+	int (*get_cc)(struct tcpc_dev *dev, enum typec_cc_status *cc1,
+		      enum typec_cc_status *cc2);
+	int (*set_polarity)(struct tcpc_dev *dev,
+			    enum typec_cc_polarity polarity);
+	int (*set_vconn)(struct tcpc_dev *dev, bool on);
+	int (*set_vbus)(struct tcpc_dev *dev, bool on, bool charge);
+	int (*set_current_limit)(struct tcpc_dev *dev, u32 max_ma, u32 mv);
+	int (*set_pd_rx)(struct tcpc_dev *dev, bool on);
+	int (*set_roles)(struct tcpc_dev *dev, bool attached,
+			 enum typec_role role, enum typec_data_role data);
+	int (*start_drp_toggling)(struct tcpc_dev *dev,
+				  enum typec_cc_status cc);
+	int (*try_role)(struct tcpc_dev *dev, int role);
+	int (*pd_transmit)(struct tcpc_dev *dev, enum tcpm_transmit_type type,
+			   const struct pd_message *msg);
+	struct tcpc_mux_dev *mux;
+};
+
+struct tcpm_port;
+
+struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc);
+void tcpm_unregister_port(struct tcpm_port *port);
+
+void tcpm_update_source_capabilities(struct tcpm_port *port, const u32 *pdo,
+				     unsigned int nr_pdo);
+void tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
+				   unsigned int nr_pdo,
+				   unsigned int max_snk_mv,
+				   unsigned int max_snk_ma,
+				   unsigned int max_snk_mw,
+				   unsigned int operating_snk_mw);
+
+void tcpm_vbus_change(struct tcpm_port *port);
+void tcpm_cc_change(struct tcpm_port *port);
+void tcpm_pd_receive(struct tcpm_port *port,
+		     const struct pd_message *msg);
+void tcpm_pd_transmit_complete(struct tcpm_port *port,
+			       enum tcpm_transmit_status status);
+void tcpm_pd_hard_reset(struct tcpm_port *port);
+void tcpm_tcpc_reset(struct tcpm_port *port);
+
+#endif /* __LINUX_USB_TCPM_H */

+ 13 - 1
drivers/usb/Kconfig

@@ -35,7 +35,6 @@ config USB_COMMON
 config USB_ARCH_HAS_HCD
 	def_bool y
 
-# ARM SA1111 chips have a non-PCI based "OHCI-compatible" USB host interface.
 config USB
 	tristate "Support for Host-side USB"
 	depends on USB_ARCH_HAS_HCD
@@ -73,6 +72,17 @@ config USB
 	  To compile this driver as a module, choose M here: the
 	  module will be called usbcore.
 
+config USB_PCI
+	bool "PCI based USB host interface"
+	depends on PCI
+	default y
+	---help---
+	  A lot of embeded system SOC (e.g. freescale T2080) have both
+	  PCI and USB modules. But USB module is controlled by registers
+	  directly, it have no relationship with PCI module.
+
+	  When say N here it will not build PCI related code in USB driver.
+
 if USB
 
 source "drivers/usb/core/Kconfig"
@@ -152,6 +162,8 @@ source "drivers/usb/phy/Kconfig"
 
 source "drivers/usb/gadget/Kconfig"
 
+source "drivers/usb/typec/Kconfig"
+
 config USB_LED_TRIG
 	bool "USB LED Triggers"
 	depends on LEDS_CLASS && LEDS_TRIGGERS

+ 3 - 1
drivers/usb/Makefile

@@ -14,7 +14,7 @@ obj-$(CONFIG_USB_ISP1760)	+= isp1760/
 obj-$(CONFIG_USB_MON)		+= mon/
 obj-$(CONFIG_USB_MTU3)		+= mtu3/
 
-obj-$(CONFIG_PCI)		+= host/
+obj-$(CONFIG_USB_PCI)		+= host/
 obj-$(CONFIG_USB_EHCI_HCD)	+= host/
 obj-$(CONFIG_USB_ISP116X_HCD)	+= host/
 obj-$(CONFIG_USB_OHCI_HCD)	+= host/
@@ -62,3 +62,5 @@ obj-$(CONFIG_USB_GADGET)	+= gadget/
 obj-$(CONFIG_USB_COMMON)	+= common/
 
 obj-$(CONFIG_USBIP_CORE)	+= usbip/
+
+obj-$(CONFIG_TYPEC)		+= typec/

+ 1 - 1
drivers/usb/atm/cxacru.c

@@ -474,7 +474,7 @@ static ssize_t cxacru_sysfs_store_adsl_config(struct device *dev,
 		ret = sscanf(buf + pos, "%x=%x%n", &index, &value, &tmp);
 		if (ret < 2)
 			return -EINVAL;
-		if (index < 0 || index > 0x7f)
+		if (index > 0x7f)
 			return -EINVAL;
 		if (tmp < 0 || tmp > len - pos)
 			return -EINVAL;

+ 1 - 1
drivers/usb/chipidea/Kconfig

@@ -20,7 +20,7 @@ config USB_CHIPIDEA_OF
 
 config USB_CHIPIDEA_PCI
 	tristate
-	depends on PCI
+	depends on USB_PCI
 	depends on NOP_USB_XCEIV
 	default USB_CHIPIDEA
 

+ 2 - 0
drivers/usb/chipidea/ci.h

@@ -177,6 +177,7 @@ struct hw_bank {
  * @td_pool: allocation pool for transfer descriptors
  * @gadget: device side representation for peripheral controller
  * @driver: gadget driver
+ * @resume_state: save the state of gadget suspend from
  * @hw_ep_max: total number of endpoints supported by hardware
  * @ci_hw_ep: array of endpoints
  * @ep0_dir: ep0 direction
@@ -227,6 +228,7 @@ struct ci_hdrc {
 
 	struct usb_gadget		gadget;
 	struct usb_gadget_driver	*driver;
+	enum usb_device_state		resume_state;
 	unsigned			hw_ep_max;
 	struct ci_hw_ep			ci_hw_ep[ENDPT_MAX];
 	u32				ep0_dir;

+ 61 - 6
drivers/usb/chipidea/core.c

@@ -783,9 +783,6 @@ struct platform_device *ci_hdrc_add_device(struct device *dev,
 	}
 
 	pdev->dev.parent = dev;
-	pdev->dev.dma_mask = dev->dma_mask;
-	pdev->dev.dma_parms = dev->dma_parms;
-	dma_set_coherent_mask(&pdev->dev, dev->coherent_dma_mask);
 
 	ret = platform_device_add_resources(pdev, res, nres);
 	if (ret)
@@ -841,6 +838,56 @@ static void ci_get_otg_capable(struct ci_hdrc *ci)
 	}
 }
 
+static ssize_t ci_role_show(struct device *dev, struct device_attribute *attr,
+			  char *buf)
+{
+	struct ci_hdrc *ci = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%s\n", ci_role(ci)->name);
+}
+
+static ssize_t ci_role_store(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t n)
+{
+	struct ci_hdrc *ci = dev_get_drvdata(dev);
+	enum ci_role role;
+	int ret;
+
+	if (!(ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET])) {
+		dev_warn(dev, "Current configuration is not dual-role, quit\n");
+		return -EPERM;
+	}
+
+	for (role = CI_ROLE_HOST; role < CI_ROLE_END; role++)
+		if (!strncmp(buf, ci->roles[role]->name,
+			     strlen(ci->roles[role]->name)))
+			break;
+
+	if (role == CI_ROLE_END || role == ci->role)
+		return -EINVAL;
+
+	pm_runtime_get_sync(dev);
+	disable_irq(ci->irq);
+	ci_role_stop(ci);
+	ret = ci_role_start(ci, role);
+	if (!ret && ci->role == CI_ROLE_GADGET)
+		ci_handle_vbus_change(ci);
+	enable_irq(ci->irq);
+	pm_runtime_put_sync(dev);
+
+	return (ret == 0) ? n : ret;
+}
+static DEVICE_ATTR(role, 0644, ci_role_show, ci_role_store);
+
+static struct attribute *ci_attrs[] = {
+	&dev_attr_role.attr,
+	NULL,
+};
+
+static struct attribute_group ci_attr_group = {
+	.attrs = ci_attrs,
+};
+
 static int ci_hdrc_probe(struct platform_device *pdev)
 {
 	struct device	*dev = &pdev->dev;
@@ -1007,11 +1054,18 @@ static int ci_hdrc_probe(struct platform_device *pdev)
 		ci_hdrc_otg_fsm_start(ci);
 
 	device_set_wakeup_capable(&pdev->dev, true);
-
 	ret = dbg_create_files(ci);
-	if (!ret)
-		return 0;
+	if (ret)
+		goto stop;
+
+	ret = sysfs_create_group(&dev->kobj, &ci_attr_group);
+	if (ret)
+		goto remove_debug;
+
+	return 0;
 
+remove_debug:
+	dbg_remove_files(ci);
 stop:
 	ci_role_destroy(ci);
 deinit_phy:
@@ -1033,6 +1087,7 @@ static int ci_hdrc_remove(struct platform_device *pdev)
 	}
 
 	dbg_remove_files(ci);
+	sysfs_remove_group(&ci->dev->kobj, &ci_attr_group);
 	ci_role_destroy(ci);
 	ci_hdrc_enter_lpm(ci, true);
 	ci_usb_phy_exit(ci);

+ 2 - 1
drivers/usb/chipidea/host.c

@@ -123,7 +123,8 @@ static int host_start(struct ci_hdrc *ci)
 	if (usb_disabled())
 		return -ENODEV;
 
-	hcd = usb_create_hcd(&ci_ehci_hc_driver, ci->dev, dev_name(ci->dev));
+	hcd = __usb_create_hcd(&ci_ehci_hc_driver, ci->dev->parent,
+			       ci->dev, dev_name(ci->dev), NULL);
 	if (!hcd)
 		return -ENOMEM;
 

+ 21 - 12
drivers/usb/chipidea/udc.c

@@ -423,7 +423,8 @@ static int _hardware_enqueue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
 
 	hwreq->req.status = -EALREADY;
 
-	ret = usb_gadget_map_request(&ci->gadget, &hwreq->req, hwep->dir);
+	ret = usb_gadget_map_request_by_dev(ci->dev->parent,
+					    &hwreq->req, hwep->dir);
 	if (ret)
 		return ret;
 
@@ -603,7 +604,8 @@ static int _hardware_dequeue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
 		list_del_init(&node->td);
 	}
 
-	usb_gadget_unmap_request(&hwep->ci->gadget, &hwreq->req, hwep->dir);
+	usb_gadget_unmap_request_by_dev(hwep->ci->dev->parent,
+					&hwreq->req, hwep->dir);
 
 	hwreq->req.actual += actual;
 
@@ -1845,27 +1847,32 @@ static irqreturn_t udc_irq(struct ci_hdrc *ci)
 		if (USBi_PCI & intr) {
 			ci->gadget.speed = hw_port_is_high_speed(ci) ?
 				USB_SPEED_HIGH : USB_SPEED_FULL;
-			if (ci->suspended && ci->driver->resume) {
-				spin_unlock(&ci->lock);
-				ci->driver->resume(&ci->gadget);
-				spin_lock(&ci->lock);
+			if (ci->suspended) {
+				if (ci->driver->resume) {
+					spin_unlock(&ci->lock);
+					ci->driver->resume(&ci->gadget);
+					spin_lock(&ci->lock);
+				}
 				ci->suspended = 0;
+				usb_gadget_set_state(&ci->gadget,
+						ci->resume_state);
 			}
 		}
 
 		if (USBi_UI  & intr)
 			isr_tr_complete_handler(ci);
 
-		if (USBi_SLI & intr) {
+		if ((USBi_SLI & intr) && !(ci->suspended)) {
+			ci->suspended = 1;
+			ci->resume_state = ci->gadget.state;
 			if (ci->gadget.speed != USB_SPEED_UNKNOWN &&
 			    ci->driver->suspend) {
-				ci->suspended = 1;
 				spin_unlock(&ci->lock);
 				ci->driver->suspend(&ci->gadget);
-				usb_gadget_set_state(&ci->gadget,
-						USB_STATE_SUSPENDED);
 				spin_lock(&ci->lock);
 			}
+			usb_gadget_set_state(&ci->gadget,
+					USB_STATE_SUSPENDED);
 		}
 		retval = IRQ_HANDLED;
 	} else {
@@ -1899,13 +1906,13 @@ static int udc_start(struct ci_hdrc *ci)
 	INIT_LIST_HEAD(&ci->gadget.ep_list);
 
 	/* alloc resources */
-	ci->qh_pool = dma_pool_create("ci_hw_qh", dev,
+	ci->qh_pool = dma_pool_create("ci_hw_qh", dev->parent,
 				       sizeof(struct ci_hw_qh),
 				       64, CI_HDRC_PAGE_SIZE);
 	if (ci->qh_pool == NULL)
 		return -ENOMEM;
 
-	ci->td_pool = dma_pool_create("ci_hw_td", dev,
+	ci->td_pool = dma_pool_create("ci_hw_td", dev->parent,
 				       sizeof(struct ci_hw_td),
 				       64, CI_HDRC_PAGE_SIZE);
 	if (ci->td_pool == NULL) {
@@ -1973,6 +1980,8 @@ static void udc_id_switch_for_host(struct ci_hdrc *ci)
 	 */
 	if (ci->is_otg)
 		hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, OTGSC_BSVIS);
+
+	ci->vbus_active = 0;
 }
 
 /**

+ 1 - 1
drivers/usb/class/Kconfig

@@ -12,7 +12,7 @@ config USB_ACM
 	  Please read <file:Documentation/usb/acm.txt> for details.
 
 	  If your modem only reports "Cls=ff(vend.)" in the descriptors in
-	  /proc/bus/usb/devices, then your modem will not work with this
+	  /sys/kernel/debug/usb/devices, then your modem will not work with this
 	  driver.
 
 	  To compile this driver as a module, choose M here: the

+ 101 - 50
drivers/usb/class/cdc-acm.c

@@ -36,6 +36,7 @@
 #include <linux/errno.h>
 #include <linux/init.h>
 #include <linux/slab.h>
+#include <linux/log2.h>
 #include <linux/tty.h>
 #include <linux/serial.h>
 #include <linux/tty_driver.h>
@@ -283,39 +284,13 @@ static DEVICE_ATTR(iCountryCodeRelDate, S_IRUGO, show_country_rel_date, NULL);
  * Interrupt handlers for various ACM device responses
  */
 
-/* control interface reports status changes with "interrupt" transfers */
-static void acm_ctrl_irq(struct urb *urb)
+static void acm_process_notification(struct acm *acm, unsigned char *buf)
 {
-	struct acm *acm = urb->context;
-	struct usb_cdc_notification *dr = urb->transfer_buffer;
-	unsigned char *data;
 	int newctrl;
 	int difference;
-	int retval;
-	int status = urb->status;
+	struct usb_cdc_notification *dr = (struct usb_cdc_notification *)buf;
+	unsigned char *data = buf + sizeof(struct usb_cdc_notification);
 
-	switch (status) {
-	case 0:
-		/* success */
-		break;
-	case -ECONNRESET:
-	case -ENOENT:
-	case -ESHUTDOWN:
-		/* this urb is terminated, clean up */
-		dev_dbg(&acm->control->dev,
-			"%s - urb shutting down with status: %d\n",
-			__func__, status);
-		return;
-	default:
-		dev_dbg(&acm->control->dev,
-			"%s - nonzero urb status received: %d\n",
-			__func__, status);
-		goto exit;
-	}
-
-	usb_mark_last_busy(acm->dev);
-
-	data = (unsigned char *)(dr + 1);
 	switch (dr->bNotificationType) {
 	case USB_CDC_NOTIFY_NETWORK_CONNECTION:
 		dev_dbg(&acm->control->dev,
@@ -323,7 +298,15 @@ static void acm_ctrl_irq(struct urb *urb)
 		break;
 
 	case USB_CDC_NOTIFY_SERIAL_STATE:
+		if (le16_to_cpu(dr->wLength) != 2) {
+			dev_dbg(&acm->control->dev,
+				"%s - malformed serial state\n", __func__);
+			break;
+		}
+
 		newctrl = get_unaligned_le16(data);
+		dev_dbg(&acm->control->dev,
+			"%s - serial state: 0x%x\n", __func__, newctrl);
 
 		if (!acm->clocal && (acm->ctrlin & ~newctrl & ACM_CTRL_DCD)) {
 			dev_dbg(&acm->control->dev,
@@ -359,13 +342,86 @@ static void acm_ctrl_irq(struct urb *urb)
 
 	default:
 		dev_dbg(&acm->control->dev,
-			"%s - unknown notification %d received: index %d "
-			"len %d data0 %d data1 %d\n",
+			"%s - unknown notification %d received: index %d len %d\n",
 			__func__,
-			dr->bNotificationType, dr->wIndex,
-			dr->wLength, data[0], data[1]);
+			dr->bNotificationType, dr->wIndex, dr->wLength);
+	}
+}
+
+/* control interface reports status changes with "interrupt" transfers */
+static void acm_ctrl_irq(struct urb *urb)
+{
+	struct acm *acm = urb->context;
+	struct usb_cdc_notification *dr = urb->transfer_buffer;
+	unsigned int current_size = urb->actual_length;
+	unsigned int expected_size, copy_size, alloc_size;
+	int retval;
+	int status = urb->status;
+
+	switch (status) {
+	case 0:
+		/* success */
 		break;
+	case -ECONNRESET:
+	case -ENOENT:
+	case -ESHUTDOWN:
+		/* this urb is terminated, clean up */
+		acm->nb_index = 0;
+		dev_dbg(&acm->control->dev,
+			"%s - urb shutting down with status: %d\n",
+			__func__, status);
+		return;
+	default:
+		dev_dbg(&acm->control->dev,
+			"%s - nonzero urb status received: %d\n",
+			__func__, status);
+		goto exit;
+	}
+
+	usb_mark_last_busy(acm->dev);
+
+	if (acm->nb_index)
+		dr = (struct usb_cdc_notification *)acm->notification_buffer;
+
+	/* size = notification-header + (optional) data */
+	expected_size = sizeof(struct usb_cdc_notification) +
+					le16_to_cpu(dr->wLength);
+
+	if (current_size < expected_size) {
+		/* notification is transmitted fragmented, reassemble */
+		if (acm->nb_size < expected_size) {
+			if (acm->nb_size) {
+				kfree(acm->notification_buffer);
+				acm->nb_size = 0;
+			}
+			alloc_size = roundup_pow_of_two(expected_size);
+			/*
+			 * kmalloc ensures a valid notification_buffer after a
+			 * use of kfree in case the previous allocation was too
+			 * small. Final freeing is done on disconnect.
+			 */
+			acm->notification_buffer =
+				kmalloc(alloc_size, GFP_ATOMIC);
+			if (!acm->notification_buffer)
+				goto exit;
+			acm->nb_size = alloc_size;
+		}
+
+		copy_size = min(current_size,
+				expected_size - acm->nb_index);
+
+		memcpy(&acm->notification_buffer[acm->nb_index],
+		       urb->transfer_buffer, copy_size);
+		acm->nb_index += copy_size;
+		current_size = acm->nb_index;
+	}
+
+	if (current_size >= expected_size) {
+		/* notification complete */
+		acm_process_notification(acm, (unsigned char *)dr);
+		acm->nb_index = 0;
 	}
+
 exit:
 	retval = usb_submit_urb(urb, GFP_ATOMIC);
 	if (retval && retval != -EPERM)
@@ -1174,6 +1230,7 @@ static int acm_probe(struct usb_interface *intf,
 	int combined_interfaces = 0;
 	struct device *tty_dev;
 	int rv = -ENOMEM;
+	int res;
 
 	/* normal quirks */
 	quirks = (unsigned long)id->driver_info;
@@ -1274,23 +1331,12 @@ static int acm_probe(struct usb_interface *intf,
 			return -EINVAL;
 		}
 look_for_collapsed_interface:
-		for (i = 0; i < 3; i++) {
-			struct usb_endpoint_descriptor *ep;
-			ep = &data_interface->cur_altsetting->endpoint[i].desc;
-
-			if (usb_endpoint_is_int_in(ep))
-				epctrl = ep;
-			else if (usb_endpoint_is_bulk_out(ep))
-				epwrite = ep;
-			else if (usb_endpoint_is_bulk_in(ep))
-				epread = ep;
-			else
-				return -EINVAL;
-		}
-		if (!epctrl || !epread || !epwrite)
-			return -ENODEV;
-		else
-			goto made_compressed_probe;
+		res = usb_find_common_endpoints(data_interface->cur_altsetting,
+				&epread, &epwrite, &epctrl, NULL);
+		if (res)
+			return res;
+
+		goto made_compressed_probe;
 	}
 
 skip_normal_probe:
@@ -1488,6 +1534,9 @@ skip_countries:
 			 epctrl->bInterval ? epctrl->bInterval : 16);
 	acm->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
 	acm->ctrlurb->transfer_dma = acm->ctrl_dma;
+	acm->notification_buffer = NULL;
+	acm->nb_index = 0;
+	acm->nb_size = 0;
 
 	dev_info(&intf->dev, "ttyACM%d: USB ACM device\n", minor);
 
@@ -1580,6 +1629,8 @@ static void acm_disconnect(struct usb_interface *intf)
 	usb_free_coherent(acm->dev, acm->ctrlsize, acm->ctrl_buffer, acm->ctrl_dma);
 	acm_read_buffers_free(acm);
 
+	kfree(acm->notification_buffer);
+
 	if (!acm->combined_interfaces)
 		usb_driver_release_interface(&acm_driver, intf == acm->control ?
 					acm->data : acm->control);

+ 3 - 1
drivers/usb/class/cdc-acm.h

@@ -98,7 +98,9 @@ struct acm {
 	struct acm_wb *putbuffer;			/* for acm_tty_put_char() */
 	int rx_buflimit;
 	spinlock_t read_lock;
-	int write_used;					/* number of non-empty write buffers */
+	u8 *notification_buffer;			/* to reassemble fragmented notifications */
+	unsigned int nb_index;
+	unsigned int nb_size;
 	int transmitting;
 	spinlock_t write_lock;
 	struct mutex mutex;

+ 4 - 99
drivers/usb/class/cdc-wdm.c

@@ -58,7 +58,6 @@ MODULE_DEVICE_TABLE (usb, wdm_ids);
 #define WDM_SUSPENDING		8
 #define WDM_RESETTING		9
 #define WDM_OVERFLOW		10
-#define WDM_DRAIN_ON_OPEN	11
 
 #define WDM_MAX			16
 
@@ -182,7 +181,7 @@ static void wdm_in_callback(struct urb *urb)
 				"nonzero urb status received: -ESHUTDOWN\n");
 			goto skip_error;
 		case -EPIPE:
-			dev_dbg(&desc->intf->dev,
+			dev_err(&desc->intf->dev,
 				"nonzero urb status received: -EPIPE\n");
 			break;
 		default:
@@ -210,25 +209,6 @@ static void wdm_in_callback(struct urb *urb)
 			desc->reslength = length;
 		}
 	}
-
-	/*
-	 * Handling devices with the WDM_DRAIN_ON_OPEN flag set:
-	 * If desc->resp_count is unset, then the urb was submitted
-	 * without a prior notification.  If the device returned any
-	 * data, then this implies that it had messages queued without
-	 * notifying us.  Continue reading until that queue is flushed.
-	 */
-	if (!desc->resp_count) {
-		if (!length) {
-			/* do not propagate the expected -EPIPE */
-			desc->rerr = 0;
-			goto unlock;
-		}
-		dev_dbg(&desc->intf->dev, "got %d bytes without notification\n", length);
-		set_bit(WDM_RESPONDING, &desc->flags);
-		usb_submit_urb(desc->response, GFP_ATOMIC);
-	}
-
 skip_error:
 	set_bit(WDM_READ, &desc->flags);
 	wake_up(&desc->wait);
@@ -243,7 +223,6 @@ skip_error:
 		service_outstanding_interrupt(desc);
 	}
 
-unlock:
 	spin_unlock(&desc->iuspin);
 }
 
@@ -686,17 +665,6 @@ static int wdm_open(struct inode *inode, struct file *file)
 			dev_err(&desc->intf->dev,
 				"Error submitting int urb - %d\n", rv);
 			rv = usb_translate_errors(rv);
-		} else if (test_bit(WDM_DRAIN_ON_OPEN, &desc->flags)) {
-			/*
-			 * Some devices keep pending messages queued
-			 * without resending notifications.  We must
-			 * flush the message queue before we can
-			 * assume a one-to-one relationship between
-			 * notifications and messages in the queue
-			 */
-			dev_dbg(&desc->intf->dev, "draining queued data\n");
-			set_bit(WDM_RESPONDING, &desc->flags);
-			rv = usb_submit_urb(desc->response, GFP_KERNEL);
 		}
 	} else {
 		rv = 0;
@@ -803,8 +771,7 @@ static void wdm_rxwork(struct work_struct *work)
 /* --- hotplug --- */
 
 static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor *ep,
-		u16 bufsize, int (*manage_power)(struct usb_interface *, int),
-		bool drain_on_open)
+		u16 bufsize, int (*manage_power)(struct usb_interface *, int))
 {
 	int rv = -ENOMEM;
 	struct wdm_device *desc;
@@ -891,68 +858,6 @@ static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor
 
 	desc->manage_power = manage_power;
 
-	/*
-	 * "drain_on_open" enables a hack to work around a firmware
-	 * issue observed on network functions, in particular MBIM
-	 * functions.
-	 *
-	 * Quoting section 7 of the CDC-WMC r1.1 specification:
-	 *
-	 *  "The firmware shall interpret GetEncapsulatedResponse as a
-	 *   request to read response bytes. The firmware shall send
-	 *   the next wLength bytes from the response. The firmware
-	 *   shall allow the host to retrieve data using any number of
-	 *   GetEncapsulatedResponse requests. The firmware shall
-	 *   return a zero- length reply if there are no data bytes
-	 *   available.
-	 *
-	 *   The firmware shall send ResponseAvailable notifications
-	 *   periodically, using any appropriate algorithm, to inform
-	 *   the host that there is data available in the reply
-	 *   buffer. The firmware is allowed to send ResponseAvailable
-	 *   notifications even if there is no data available, but
-	 *   this will obviously reduce overall performance."
-	 *
-	 * These requirements, although they make equally sense, are
-	 * often not implemented by network functions. Some firmwares
-	 * will queue data indefinitely, without ever resending a
-	 * notification. The result is that the driver and firmware
-	 * loses "syncronization" if the driver ever fails to respond
-	 * to a single notification, something which easily can happen
-	 * on release(). When this happens, the driver will appear to
-	 * never receive notifications for the most current data. Each
-	 * notification will only cause a single read, which returns
-	 * the oldest data in the firmware's queue.
-	 *
-	 * The "drain_on_open" hack resolves the situation by draining
-	 * data from the firmware until none is returned, without a
-	 * prior notification.
-	 *
-	 * This will inevitably race with the firmware, risking that
-	 * we read data from the device before handling the associated
-	 * notification. To make things worse, some of the devices
-	 * needing the hack do not implement the "return zero if no
-	 * data is available" requirement either. Instead they return
-	 * an error on the subsequent read in this case.  This means
-	 * that "winning" the race can cause an unexpected EIO to
-	 * userspace.
-	 *
-	 * "winning" the race is more likely on resume() than on
-	 * open(), and the unexpected error is more harmful in the
-	 * middle of an open session. The hack is therefore only
-	 * applied on open(), and not on resume() where it logically
-	 * would be equally necessary. So we define open() as the only
-	 * driver <-> device "syncronization point".  Should we happen
-	 * to lose a notification after open(), then syncronization
-	 * will be lost until release()
-	 *
-	 * The hack should not be enabled for CDC WDM devices
-	 * conforming to the CDC-WMC r1.1 specification.  This is
-	 * ensured by setting drain_on_open to false in wdm_probe().
-	 */
-	if (drain_on_open)
-		set_bit(WDM_DRAIN_ON_OPEN, &desc->flags);
-
 	spin_lock(&wdm_device_list_lock);
 	list_add(&desc->device_list, &wdm_device_list);
 	spin_unlock(&wdm_device_list_lock);
@@ -1006,7 +911,7 @@ static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id)
 		goto err;
 	ep = &iface->endpoint[0].desc;
 
-	rv = wdm_create(intf, ep, maxcom, &wdm_manage_power, false);
+	rv = wdm_create(intf, ep, maxcom, &wdm_manage_power);
 
 err:
 	return rv;
@@ -1038,7 +943,7 @@ struct usb_driver *usb_cdc_wdm_register(struct usb_interface *intf,
 {
 	int rv = -EINVAL;
 
-	rv = wdm_create(intf, ep, bufsize, manage_power, true);
+	rv = wdm_create(intf, ep, bufsize, manage_power);
 	if (rv < 0)
 		goto err;
 

+ 14 - 23
drivers/usb/class/usblp.c

@@ -294,7 +294,7 @@ static int usblp_ctrl_msg(struct usblp *usblp, int request, int type, int dir, i
 
 /*
  * See the description for usblp_select_alts() below for the usage
- * explanation.  Look into your /proc/bus/usb/devices and dmesg in
+ * explanation.  Look into your /sys/kernel/debug/usb/devices and dmesg in
  * case of any trouble.
  */
 static int proto_bias = -1;
@@ -1239,8 +1239,9 @@ static int usblp_select_alts(struct usblp *usblp)
 {
 	struct usb_interface *if_alt;
 	struct usb_host_interface *ifd;
-	struct usb_endpoint_descriptor *epd, *epwrite, *epread;
-	int p, i, e;
+	struct usb_endpoint_descriptor *epwrite, *epread;
+	int p, i;
+	int res;
 
 	if_alt = usblp->intf;
 
@@ -1260,31 +1261,21 @@ static int usblp_select_alts(struct usblp *usblp)
 		    ifd->desc.bInterfaceProtocol > USBLP_LAST_PROTOCOL)
 			continue;
 
-		/* Look for bulk OUT and IN endpoints. */
-		epwrite = epread = NULL;
-		for (e = 0; e < ifd->desc.bNumEndpoints; e++) {
-			epd = &ifd->endpoint[e].desc;
-
-			if (usb_endpoint_is_bulk_out(epd))
-				if (!epwrite)
-					epwrite = epd;
-
-			if (usb_endpoint_is_bulk_in(epd))
-				if (!epread)
-					epread = epd;
+		/* Look for the expected bulk endpoints. */
+		if (ifd->desc.bInterfaceProtocol > 1) {
+			res = usb_find_common_endpoints(ifd,
+					&epread, &epwrite, NULL, NULL);
+		} else {
+			epread = NULL;
+			res = usb_find_bulk_out_endpoint(ifd, &epwrite);
 		}
 
 		/* Ignore buggy hardware without the right endpoints. */
-		if (!epwrite || (ifd->desc.bInterfaceProtocol > 1 && !epread))
+		if (res)
 			continue;
 
-		/*
-		 * Turn off reads for USB_CLASS_PRINTER/1/1 (unidirectional)
-		 * interfaces and buggy bidirectional printers.
-		 */
-		if (ifd->desc.bInterfaceProtocol == 1) {
-			epread = NULL;
-		} else if (usblp->quirks & USBLP_QUIRK_BIDIR) {
+		/* Turn off reads for buggy bidirectional printers. */
+		if (usblp->quirks & USBLP_QUIRK_BIDIR) {
 			printk(KERN_INFO "usblp%d: Disabling reads from "
 			    "problematic bidirectional printer\n",
 			    usblp->minor);

+ 18 - 38
drivers/usb/class/usbtmc.c

@@ -1375,7 +1375,7 @@ static int usbtmc_probe(struct usb_interface *intf,
 {
 	struct usbtmc_device_data *data;
 	struct usb_host_interface *iface_desc;
-	struct usb_endpoint_descriptor *endpoint;
+	struct usb_endpoint_descriptor *bulk_in, *bulk_out, *int_in;
 	int n;
 	int retcode;
 
@@ -1421,49 +1421,29 @@ static int usbtmc_probe(struct usb_interface *intf,
 	iface_desc = data->intf->cur_altsetting;
 	data->ifnum = iface_desc->desc.bInterfaceNumber;
 
-	/* Find bulk in endpoint */
-	for (n = 0; n < iface_desc->desc.bNumEndpoints; n++) {
-		endpoint = &iface_desc->endpoint[n].desc;
-
-		if (usb_endpoint_is_bulk_in(endpoint)) {
-			data->bulk_in = endpoint->bEndpointAddress;
-			dev_dbg(&intf->dev, "Found bulk in endpoint at %u\n",
-				data->bulk_in);
-			break;
-		}
-	}
-
-	/* Find bulk out endpoint */
-	for (n = 0; n < iface_desc->desc.bNumEndpoints; n++) {
-		endpoint = &iface_desc->endpoint[n].desc;
-
-		if (usb_endpoint_is_bulk_out(endpoint)) {
-			data->bulk_out = endpoint->bEndpointAddress;
-			dev_dbg(&intf->dev, "Found Bulk out endpoint at %u\n",
-				data->bulk_out);
-			break;
-		}
-	}
-
-	if (!data->bulk_out || !data->bulk_in) {
+	/* Find bulk endpoints */
+	retcode = usb_find_common_endpoints(iface_desc,
+			&bulk_in, &bulk_out, NULL, NULL);
+	if (retcode) {
 		dev_err(&intf->dev, "bulk endpoints not found\n");
-		retcode = -ENODEV;
 		goto err_put;
 	}
 
+	data->bulk_in = bulk_in->bEndpointAddress;
+	dev_dbg(&intf->dev, "Found bulk in endpoint at %u\n", data->bulk_in);
+
+	data->bulk_out = bulk_out->bEndpointAddress;
+	dev_dbg(&intf->dev, "Found Bulk out endpoint at %u\n", data->bulk_out);
+
 	/* Find int endpoint */
-	for (n = 0; n < iface_desc->desc.bNumEndpoints; n++) {
-		endpoint = &iface_desc->endpoint[n].desc;
-
-		if (usb_endpoint_is_int_in(endpoint)) {
-			data->iin_ep_present = 1;
-			data->iin_ep = endpoint->bEndpointAddress;
-			data->iin_wMaxPacketSize = usb_endpoint_maxp(endpoint);
-			data->iin_interval = endpoint->bInterval;
-			dev_dbg(&intf->dev, "Found Int in endpoint at %u\n",
+	retcode = usb_find_int_in_endpoint(iface_desc, &int_in);
+	if (!retcode) {
+		data->iin_ep_present = 1;
+		data->iin_ep = int_in->bEndpointAddress;
+		data->iin_wMaxPacketSize = usb_endpoint_maxp(int_in);
+		data->iin_interval = int_in->bInterval;
+		dev_dbg(&intf->dev, "Found Int in endpoint at %u\n",
 				data->iin_ep);
-			break;
-		}
 	}
 
 	retcode = get_capabilities(data);

+ 7 - 0
drivers/usb/common/usb-otg-fsm.c

@@ -31,6 +31,13 @@
 #include <linux/usb/otg.h>
 #include <linux/usb/otg-fsm.h>
 
+#ifdef VERBOSE
+#define VDBG(fmt, args...) pr_debug("[%s]  " fmt, \
+				 __func__, ## args)
+#else
+#define VDBG(stuff...)	do {} while (0)
+#endif
+
 /* Change USB protocol when there is a protocol change */
 static int otg_set_protocol(struct otg_fsm *fsm, int protocol)
 {

+ 1 - 1
drivers/usb/core/Makefile

@@ -8,7 +8,7 @@ usbcore-y += devio.o notify.o generic.o quirks.o devices.o
 usbcore-y += port.o
 
 usbcore-$(CONFIG_OF)		+= of.o
-usbcore-$(CONFIG_PCI)		+= hcd-pci.o
+usbcore-$(CONFIG_USB_PCI)		+= hcd-pci.o
 usbcore-$(CONFIG_ACPI)		+= usb-acpi.o
 
 obj-$(CONFIG_USB)		+= usbcore.o

+ 6 - 6
drivers/usb/core/buffer.c

@@ -66,7 +66,7 @@ int hcd_buffer_create(struct usb_hcd *hcd)
 	int		i, size;
 
 	if (!IS_ENABLED(CONFIG_HAS_DMA) ||
-	    (!hcd->self.controller->dma_mask &&
+	    (!is_device_dma_capable(hcd->self.sysdev) &&
 	     !(hcd->driver->flags & HCD_LOCAL_MEM)))
 		return 0;
 
@@ -75,7 +75,7 @@ int hcd_buffer_create(struct usb_hcd *hcd)
 		if (!size)
 			continue;
 		snprintf(name, sizeof(name), "buffer-%d", size);
-		hcd->pool[i] = dma_pool_create(name, hcd->self.controller,
+		hcd->pool[i] = dma_pool_create(name, hcd->self.sysdev,
 				size, size, 0);
 		if (!hcd->pool[i]) {
 			hcd_buffer_destroy(hcd);
@@ -130,7 +130,7 @@ void *hcd_buffer_alloc(
 
 	/* some USB hosts just use PIO */
 	if (!IS_ENABLED(CONFIG_HAS_DMA) ||
-	    (!bus->controller->dma_mask &&
+	    (!is_device_dma_capable(bus->sysdev) &&
 	     !(hcd->driver->flags & HCD_LOCAL_MEM))) {
 		*dma = ~(dma_addr_t) 0;
 		return kmalloc(size, mem_flags);
@@ -140,7 +140,7 @@ void *hcd_buffer_alloc(
 		if (size <= pool_max[i])
 			return dma_pool_alloc(hcd->pool[i], mem_flags, dma);
 	}
-	return dma_alloc_coherent(hcd->self.controller, size, dma, mem_flags);
+	return dma_alloc_coherent(hcd->self.sysdev, size, dma, mem_flags);
 }
 
 void hcd_buffer_free(
@@ -157,7 +157,7 @@ void hcd_buffer_free(
 		return;
 
 	if (!IS_ENABLED(CONFIG_HAS_DMA) ||
-	    (!bus->controller->dma_mask &&
+	    (!is_device_dma_capable(bus->sysdev) &&
 	     !(hcd->driver->flags & HCD_LOCAL_MEM))) {
 		kfree(addr);
 		return;
@@ -169,5 +169,5 @@ void hcd_buffer_free(
 			return;
 		}
 	}
-	dma_free_coherent(hcd->self.controller, size, addr, dma);
+	dma_free_coherent(hcd->self.sysdev, size, addr, dma);
 }

+ 2 - 2
drivers/usb/core/devices.c

@@ -24,7 +24,7 @@
  * <mountpoint>/devices contains USB topology, device, config, class,
  * interface, & endpoint data.
  *
- * I considered using /proc/bus/usb/devices/device# for each device
+ * I considered using /dev/bus/usb/device# for each device
  * as it is attached or detached, but I didn't like this for some
  * reason -- maybe it's just too deep of a directory structure.
  * I also don't like looking in multiple places to gather and view
@@ -40,7 +40,7 @@
  *   Converted the whole proc stuff to real
  *   read methods. Now not the whole device list needs to fit
  *   into one page, only the device list for one bus.
- *   Added a poll method to /proc/bus/usb/devices, to wake
+ *   Added a poll method to /sys/kernel/debug/usb/devices, to wake
  *   up an eventual usbd
  * 2000-01-04: Thomas Sailer <sailer@ife.ee.ethz.ch>
  *   Turned into its own filesystem

+ 21 - 0
drivers/usb/core/driver.c

@@ -1331,6 +1331,24 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
 		 */
 		if (udev->parent && !PMSG_IS_AUTO(msg))
 			status = 0;
+
+		/*
+		 * If the device is inaccessible, don't try to resume
+		 * suspended interfaces and just return the error.
+		 */
+		if (status && status != -EBUSY) {
+			int err;
+			u16 devstat;
+
+			err = usb_get_status(udev, USB_RECIP_DEVICE, 0,
+					     &devstat);
+			if (err) {
+				dev_err(&udev->dev,
+					"Failed to suspend device, error %d\n",
+					status);
+				goto done;
+			}
+		}
 	}
 
 	/* If the suspend failed, resume interfaces that did get suspended */
@@ -1763,6 +1781,9 @@ static int autosuspend_check(struct usb_device *udev)
 	int			w, i;
 	struct usb_interface	*intf;
 
+	if (udev->state == USB_STATE_NOTATTACHED)
+		return -ENODEV;
+
 	/* Fail if autosuspend is disabled, or any interfaces are in use, or
 	 * any interface drivers require remote wakeup but it isn't available.
 	 */

+ 7 - 2
drivers/usb/core/file.c

@@ -29,6 +29,7 @@
 #define MAX_USB_MINORS	256
 static const struct file_operations *usb_minors[MAX_USB_MINORS];
 static DECLARE_RWSEM(minor_rwsem);
+static DEFINE_MUTEX(init_usb_class_mutex);
 
 static int usb_open(struct inode *inode, struct file *file)
 {
@@ -111,8 +112,9 @@ static void release_usb_class(struct kref *kref)
 
 static void destroy_usb_class(void)
 {
-	if (usb_class)
-		kref_put(&usb_class->kref, release_usb_class);
+	mutex_lock(&init_usb_class_mutex);
+	kref_put(&usb_class->kref, release_usb_class);
+	mutex_unlock(&init_usb_class_mutex);
 }
 
 int usb_major_init(void)
@@ -173,7 +175,10 @@ int usb_register_dev(struct usb_interface *intf,
 	if (intf->minor >= 0)
 		return -EADDRINUSE;
 
+	mutex_lock(&init_usb_class_mutex);
 	retval = init_usb_class();
+	mutex_unlock(&init_usb_class_mutex);
+
 	if (retval)
 		return retval;
 

+ 46 - 36
drivers/usb/core/hcd.c

@@ -1076,6 +1076,7 @@ static void usb_deregister_bus (struct usb_bus *bus)
 static int register_root_hub(struct usb_hcd *hcd)
 {
 	struct device *parent_dev = hcd->self.controller;
+	struct device *sysdev = hcd->self.sysdev;
 	struct usb_device *usb_dev = hcd->self.root_hub;
 	const int devnum = 1;
 	int retval;
@@ -1122,7 +1123,7 @@ static int register_root_hub(struct usb_hcd *hcd)
 		/* Did the HC die before the root hub was registered? */
 		if (HCD_DEAD(hcd))
 			usb_hc_died (hcd);	/* This time clean up */
-		usb_dev->dev.of_node = parent_dev->of_node;
+		usb_dev->dev.of_node = sysdev->of_node;
 	}
 	mutex_unlock(&usb_bus_idr_lock);
 
@@ -1435,7 +1436,7 @@ void usb_hcd_unmap_urb_setup_for_dma(struct usb_hcd *hcd, struct urb *urb)
 {
 	if (IS_ENABLED(CONFIG_HAS_DMA) &&
 	    (urb->transfer_flags & URB_SETUP_MAP_SINGLE))
-		dma_unmap_single(hcd->self.controller,
+		dma_unmap_single(hcd->self.sysdev,
 				urb->setup_dma,
 				sizeof(struct usb_ctrlrequest),
 				DMA_TO_DEVICE);
@@ -1468,19 +1469,19 @@ void usb_hcd_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb)
 	dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
 	if (IS_ENABLED(CONFIG_HAS_DMA) &&
 	    (urb->transfer_flags & URB_DMA_MAP_SG))
-		dma_unmap_sg(hcd->self.controller,
+		dma_unmap_sg(hcd->self.sysdev,
 				urb->sg,
 				urb->num_sgs,
 				dir);
 	else if (IS_ENABLED(CONFIG_HAS_DMA) &&
 		 (urb->transfer_flags & URB_DMA_MAP_PAGE))
-		dma_unmap_page(hcd->self.controller,
+		dma_unmap_page(hcd->self.sysdev,
 				urb->transfer_dma,
 				urb->transfer_buffer_length,
 				dir);
 	else if (IS_ENABLED(CONFIG_HAS_DMA) &&
 		 (urb->transfer_flags & URB_DMA_MAP_SINGLE))
-		dma_unmap_single(hcd->self.controller,
+		dma_unmap_single(hcd->self.sysdev,
 				urb->transfer_dma,
 				urb->transfer_buffer_length,
 				dir);
@@ -1523,11 +1524,11 @@ int usb_hcd_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
 			return ret;
 		if (IS_ENABLED(CONFIG_HAS_DMA) && hcd->self.uses_dma) {
 			urb->setup_dma = dma_map_single(
-					hcd->self.controller,
+					hcd->self.sysdev,
 					urb->setup_packet,
 					sizeof(struct usb_ctrlrequest),
 					DMA_TO_DEVICE);
-			if (dma_mapping_error(hcd->self.controller,
+			if (dma_mapping_error(hcd->self.sysdev,
 						urb->setup_dma))
 				return -EAGAIN;
 			urb->transfer_flags |= URB_SETUP_MAP_SINGLE;
@@ -1558,7 +1559,7 @@ int usb_hcd_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
 				}
 
 				n = dma_map_sg(
-						hcd->self.controller,
+						hcd->self.sysdev,
 						urb->sg,
 						urb->num_sgs,
 						dir);
@@ -1573,12 +1574,12 @@ int usb_hcd_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
 			} else if (urb->sg) {
 				struct scatterlist *sg = urb->sg;
 				urb->transfer_dma = dma_map_page(
-						hcd->self.controller,
+						hcd->self.sysdev,
 						sg_page(sg),
 						sg->offset,
 						urb->transfer_buffer_length,
 						dir);
-				if (dma_mapping_error(hcd->self.controller,
+				if (dma_mapping_error(hcd->self.sysdev,
 						urb->transfer_dma))
 					ret = -EAGAIN;
 				else
@@ -1588,11 +1589,11 @@ int usb_hcd_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
 				ret = -EAGAIN;
 			} else {
 				urb->transfer_dma = dma_map_single(
-						hcd->self.controller,
+						hcd->self.sysdev,
 						urb->transfer_buffer,
 						urb->transfer_buffer_length,
 						dir);
-				if (dma_mapping_error(hcd->self.controller,
+				if (dma_mapping_error(hcd->self.sysdev,
 						urb->transfer_dma))
 					ret = -EAGAIN;
 				else
@@ -2498,24 +2499,8 @@ static void init_giveback_urb_bh(struct giveback_urb_bh *bh)
 	tasklet_init(&bh->bh, usb_giveback_urb_bh, (unsigned long)bh);
 }
 
-/**
- * usb_create_shared_hcd - create and initialize an HCD structure
- * @driver: HC driver that will use this hcd
- * @dev: device for this HC, stored in hcd->self.controller
- * @bus_name: value to store in hcd->self.bus_name
- * @primary_hcd: a pointer to the usb_hcd structure that is sharing the
- *              PCI device.  Only allocate certain resources for the primary HCD
- * Context: !in_interrupt()
- *
- * Allocate a struct usb_hcd, with extra space at the end for the
- * HC driver's private data.  Initialize the generic members of the
- * hcd structure.
- *
- * Return: On success, a pointer to the created and initialized HCD structure.
- * On failure (e.g. if memory is unavailable), %NULL.
- */
-struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver,
-		struct device *dev, const char *bus_name,
+struct usb_hcd *__usb_create_hcd(const struct hc_driver *driver,
+		struct device *sysdev, struct device *dev, const char *bus_name,
 		struct usb_hcd *primary_hcd)
 {
 	struct usb_hcd *hcd;
@@ -2556,8 +2541,9 @@ struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver,
 
 	usb_bus_init(&hcd->self);
 	hcd->self.controller = dev;
+	hcd->self.sysdev = sysdev;
 	hcd->self.bus_name = bus_name;
-	hcd->self.uses_dma = (dev->dma_mask != NULL);
+	hcd->self.uses_dma = (sysdev->dma_mask != NULL);
 
 	init_timer(&hcd->rh_timer);
 	hcd->rh_timer.function = rh_timer_func;
@@ -2572,6 +2558,30 @@ struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver,
 			"USB Host Controller";
 	return hcd;
 }
+EXPORT_SYMBOL_GPL(__usb_create_hcd);
+
+/**
+ * usb_create_shared_hcd - create and initialize an HCD structure
+ * @driver: HC driver that will use this hcd
+ * @dev: device for this HC, stored in hcd->self.controller
+ * @bus_name: value to store in hcd->self.bus_name
+ * @primary_hcd: a pointer to the usb_hcd structure that is sharing the
+ *              PCI device.  Only allocate certain resources for the primary HCD
+ * Context: !in_interrupt()
+ *
+ * Allocate a struct usb_hcd, with extra space at the end for the
+ * HC driver's private data.  Initialize the generic members of the
+ * hcd structure.
+ *
+ * Return: On success, a pointer to the created and initialized HCD structure.
+ * On failure (e.g. if memory is unavailable), %NULL.
+ */
+struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver,
+		struct device *dev, const char *bus_name,
+		struct usb_hcd *primary_hcd)
+{
+	return __usb_create_hcd(driver, dev, dev, bus_name, primary_hcd);
+}
 EXPORT_SYMBOL_GPL(usb_create_shared_hcd);
 
 /**
@@ -2591,7 +2601,7 @@ EXPORT_SYMBOL_GPL(usb_create_shared_hcd);
 struct usb_hcd *usb_create_hcd(const struct hc_driver *driver,
 		struct device *dev, const char *bus_name)
 {
-	return usb_create_shared_hcd(driver, dev, bus_name, NULL);
+	return __usb_create_hcd(driver, dev, dev, bus_name, NULL);
 }
 EXPORT_SYMBOL_GPL(usb_create_hcd);
 
@@ -2718,7 +2728,7 @@ int usb_add_hcd(struct usb_hcd *hcd,
 	struct usb_device *rhdev;
 
 	if (IS_ENABLED(CONFIG_USB_PHY) && !hcd->usb_phy) {
-		struct usb_phy *phy = usb_get_phy_dev(hcd->self.controller, 0);
+		struct usb_phy *phy = usb_get_phy_dev(hcd->self.sysdev, 0);
 
 		if (IS_ERR(phy)) {
 			retval = PTR_ERR(phy);
@@ -2736,7 +2746,7 @@ int usb_add_hcd(struct usb_hcd *hcd,
 	}
 
 	if (IS_ENABLED(CONFIG_GENERIC_PHY) && !hcd->phy) {
-		struct phy *phy = phy_get(hcd->self.controller, "usb");
+		struct phy *phy = phy_get(hcd->self.sysdev, "usb");
 
 		if (IS_ERR(phy)) {
 			retval = PTR_ERR(phy);
@@ -2784,7 +2794,7 @@ int usb_add_hcd(struct usb_hcd *hcd,
 	 */
 	retval = hcd_buffer_create(hcd);
 	if (retval != 0) {
-		dev_dbg(hcd->self.controller, "pool alloc failed\n");
+		dev_dbg(hcd->self.sysdev, "pool alloc failed\n");
 		goto err_create_buf;
 	}
 
@@ -2794,7 +2804,7 @@ int usb_add_hcd(struct usb_hcd *hcd,
 
 	rhdev = usb_alloc_dev(NULL, &hcd->self, 0);
 	if (rhdev == NULL) {
-		dev_err(hcd->self.controller, "unable to allocate root hub\n");
+		dev_err(hcd->self.sysdev, "unable to allocate root hub\n");
 		retval = -ENOMEM;
 		goto err_allocate_root_hub;
 	}

+ 10 - 1
drivers/usb/core/hub.c

@@ -1066,6 +1066,9 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
 
 		portstatus = portchange = 0;
 		status = hub_port_status(hub, port1, &portstatus, &portchange);
+		if (status)
+			goto abort;
+
 		if (udev || (portstatus & USB_PORT_STAT_CONNECTION))
 			dev_dbg(&port_dev->dev, "status %04x change %04x\n",
 					portstatus, portchange);
@@ -1198,7 +1201,7 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
 
 	/* Scan all ports that need attention */
 	kick_hub_wq(hub);
-
+ abort:
 	if (type == HUB_INIT2 || type == HUB_INIT3) {
 		/* Allow autosuspend if it was suppressed */
  disconnected:
@@ -2084,6 +2087,12 @@ void usb_disconnect(struct usb_device **pdev)
 	dev_info(&udev->dev, "USB disconnect, device number %d\n",
 			udev->devnum);
 
+	/*
+	 * Ensure that the pm runtime code knows that the USB device
+	 * is in the process of being disconnected.
+	 */
+	pm_runtime_barrier(&udev->dev);
+
 	usb_lock_device(udev);
 
 	hub_disconnect_children(udev);

+ 23 - 0
drivers/usb/core/of.c

@@ -18,6 +18,7 @@
  */
 
 #include <linux/of.h>
+#include <linux/of_platform.h>
 #include <linux/usb/of.h>
 
 /**
@@ -46,3 +47,25 @@ struct device_node *usb_of_get_child_node(struct device_node *parent,
 }
 EXPORT_SYMBOL_GPL(usb_of_get_child_node);
 
+/**
+ * usb_of_get_companion_dev - Find the companion device
+ * @dev: the device pointer to find a companion
+ *
+ * Find the companion device from platform bus.
+ *
+ * Return: On success, a pointer to the companion device, %NULL on failure.
+ */
+struct device *usb_of_get_companion_dev(struct device *dev)
+{
+	struct device_node *node;
+	struct platform_device *pdev = NULL;
+
+	node = of_parse_phandle(dev->of_node, "companion", 0);
+	if (node)
+		pdev = of_find_device_by_node(node);
+
+	of_node_put(node);
+
+	return pdev ? &pdev->dev : NULL;
+}
+EXPORT_SYMBOL_GPL(usb_of_get_companion_dev);

+ 143 - 9
drivers/usb/core/usb.c

@@ -74,6 +74,140 @@ MODULE_PARM_DESC(autosuspend, "default autosuspend delay");
 #define usb_autosuspend_delay		0
 #endif
 
+static bool match_endpoint(struct usb_endpoint_descriptor *epd,
+		struct usb_endpoint_descriptor **bulk_in,
+		struct usb_endpoint_descriptor **bulk_out,
+		struct usb_endpoint_descriptor **int_in,
+		struct usb_endpoint_descriptor **int_out)
+{
+	switch (usb_endpoint_type(epd)) {
+	case USB_ENDPOINT_XFER_BULK:
+		if (usb_endpoint_dir_in(epd)) {
+			if (bulk_in && !*bulk_in) {
+				*bulk_in = epd;
+				break;
+			}
+		} else {
+			if (bulk_out && !*bulk_out) {
+				*bulk_out = epd;
+				break;
+			}
+		}
+
+		return false;
+	case USB_ENDPOINT_XFER_INT:
+		if (usb_endpoint_dir_in(epd)) {
+			if (int_in && !*int_in) {
+				*int_in = epd;
+				break;
+			}
+		} else {
+			if (int_out && !*int_out) {
+				*int_out = epd;
+				break;
+			}
+		}
+
+		return false;
+	default:
+		return false;
+	}
+
+	return (!bulk_in || *bulk_in) && (!bulk_out || *bulk_out) &&
+			(!int_in || *int_in) && (!int_out || *int_out);
+}
+
+/**
+ * usb_find_common_endpoints() -- look up common endpoint descriptors
+ * @alt:	alternate setting to search
+ * @bulk_in:	pointer to descriptor pointer, or NULL
+ * @bulk_out:	pointer to descriptor pointer, or NULL
+ * @int_in:	pointer to descriptor pointer, or NULL
+ * @int_out:	pointer to descriptor pointer, or NULL
+ *
+ * Search the alternate setting's endpoint descriptors for the first bulk-in,
+ * bulk-out, interrupt-in and interrupt-out endpoints and return them in the
+ * provided pointers (unless they are NULL).
+ *
+ * If a requested endpoint is not found, the corresponding pointer is set to
+ * NULL.
+ *
+ * Return: Zero if all requested descriptors were found, or -ENXIO otherwise.
+ */
+int usb_find_common_endpoints(struct usb_host_interface *alt,
+		struct usb_endpoint_descriptor **bulk_in,
+		struct usb_endpoint_descriptor **bulk_out,
+		struct usb_endpoint_descriptor **int_in,
+		struct usb_endpoint_descriptor **int_out)
+{
+	struct usb_endpoint_descriptor *epd;
+	int i;
+
+	if (bulk_in)
+		*bulk_in = NULL;
+	if (bulk_out)
+		*bulk_out = NULL;
+	if (int_in)
+		*int_in = NULL;
+	if (int_out)
+		*int_out = NULL;
+
+	for (i = 0; i < alt->desc.bNumEndpoints; ++i) {
+		epd = &alt->endpoint[i].desc;
+
+		if (match_endpoint(epd, bulk_in, bulk_out, int_in, int_out))
+			return 0;
+	}
+
+	return -ENXIO;
+}
+EXPORT_SYMBOL_GPL(usb_find_common_endpoints);
+
+/**
+ * usb_find_common_endpoints_reverse() -- look up common endpoint descriptors
+ * @alt:	alternate setting to search
+ * @bulk_in:	pointer to descriptor pointer, or NULL
+ * @bulk_out:	pointer to descriptor pointer, or NULL
+ * @int_in:	pointer to descriptor pointer, or NULL
+ * @int_out:	pointer to descriptor pointer, or NULL
+ *
+ * Search the alternate setting's endpoint descriptors for the last bulk-in,
+ * bulk-out, interrupt-in and interrupt-out endpoints and return them in the
+ * provided pointers (unless they are NULL).
+ *
+ * If a requested endpoint is not found, the corresponding pointer is set to
+ * NULL.
+ *
+ * Return: Zero if all requested descriptors were found, or -ENXIO otherwise.
+ */
+int usb_find_common_endpoints_reverse(struct usb_host_interface *alt,
+		struct usb_endpoint_descriptor **bulk_in,
+		struct usb_endpoint_descriptor **bulk_out,
+		struct usb_endpoint_descriptor **int_in,
+		struct usb_endpoint_descriptor **int_out)
+{
+	struct usb_endpoint_descriptor *epd;
+	int i;
+
+	if (bulk_in)
+		*bulk_in = NULL;
+	if (bulk_out)
+		*bulk_out = NULL;
+	if (int_in)
+		*int_in = NULL;
+	if (int_out)
+		*int_out = NULL;
+
+	for (i = alt->desc.bNumEndpoints - 1; i >= 0; --i) {
+		epd = &alt->endpoint[i].desc;
+
+		if (match_endpoint(epd, bulk_in, bulk_out, int_in, int_out))
+			return 0;
+	}
+
+	return -ENXIO;
+}
+EXPORT_SYMBOL_GPL(usb_find_common_endpoints_reverse);
 
 /**
  * usb_find_alt_setting() - Given a configuration, find the alternate setting
@@ -453,9 +587,9 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent,
 	 * Note: calling dma_set_mask() on a USB device would set the
 	 * mask for the entire HCD, so don't do that.
 	 */
-	dev->dev.dma_mask = bus->controller->dma_mask;
-	dev->dev.dma_pfn_offset = bus->controller->dma_pfn_offset;
-	set_dev_node(&dev->dev, dev_to_node(bus->controller));
+	dev->dev.dma_mask = bus->sysdev->dma_mask;
+	dev->dev.dma_pfn_offset = bus->sysdev->dma_pfn_offset;
+	set_dev_node(&dev->dev, dev_to_node(bus->sysdev));
 	dev->state = USB_STATE_ATTACHED;
 	dev->lpm_disable_count = 1;
 	atomic_set(&dev->urbnum, 0);
@@ -803,7 +937,7 @@ struct urb *usb_buffer_map(struct urb *urb)
 	if (!urb
 			|| !urb->dev
 			|| !(bus = urb->dev->bus)
-			|| !(controller = bus->controller))
+			|| !(controller = bus->sysdev))
 		return NULL;
 
 	if (controller->dma_mask) {
@@ -841,7 +975,7 @@ void usb_buffer_dmasync(struct urb *urb)
 			|| !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)
 			|| !urb->dev
 			|| !(bus = urb->dev->bus)
-			|| !(controller = bus->controller))
+			|| !(controller = bus->sysdev))
 		return;
 
 	if (controller->dma_mask) {
@@ -875,7 +1009,7 @@ void usb_buffer_unmap(struct urb *urb)
 			|| !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)
 			|| !urb->dev
 			|| !(bus = urb->dev->bus)
-			|| !(controller = bus->controller))
+			|| !(controller = bus->sysdev))
 		return;
 
 	if (controller->dma_mask) {
@@ -925,7 +1059,7 @@ int usb_buffer_map_sg(const struct usb_device *dev, int is_in,
 
 	if (!dev
 			|| !(bus = dev->bus)
-			|| !(controller = bus->controller)
+			|| !(controller = bus->sysdev)
 			|| !controller->dma_mask)
 		return -EINVAL;
 
@@ -961,7 +1095,7 @@ void usb_buffer_dmasync_sg(const struct usb_device *dev, int is_in,
 
 	if (!dev
 			|| !(bus = dev->bus)
-			|| !(controller = bus->controller)
+			|| !(controller = bus->sysdev)
 			|| !controller->dma_mask)
 		return;
 
@@ -989,7 +1123,7 @@ void usb_buffer_unmap_sg(const struct usb_device *dev, int is_in,
 
 	if (!dev
 			|| !(bus = dev->bus)
-			|| !(controller = bus->controller)
+			|| !(controller = bus->sysdev)
 			|| !controller->dma_mask)
 		return;
 

+ 1 - 1
drivers/usb/dwc2/Kconfig

@@ -54,7 +54,7 @@ endchoice
 
 config USB_DWC2_PCI
 	tristate "DWC2 PCI"
-	depends on PCI
+	depends on USB_PCI
 	depends on USB_GADGET || !USB_GADGET
 	default n
 	select NOP_USB_XCEIV

+ 5 - 0
drivers/usb/dwc2/core.h

@@ -423,6 +423,10 @@ enum dwc2_ep0_state {
  *			needed.
  *			0 - No (default)
  *			1 - Yes
+ * @activate_stm_fs_transceiver: Activate internal transceiver using GGPIO
+ *			register.
+ *			0 - Deactivate the transceiver (default)
+ *			1 - Activate the transceiver
  * @g_dma:              Enables gadget dma usage (default: autodetect).
  * @g_dma_desc:         Enables gadget descriptor DMA (default: autodetect).
  * @g_rx_fifo_size:	The periodic rx fifo size for the device, in
@@ -477,6 +481,7 @@ struct dwc2_core_params {
 	bool uframe_sched;
 	bool external_id_pin_ctl;
 	bool hibernation;
+	bool activate_stm_fs_transceiver;
 	u16 max_packet_count;
 	u32 max_transfer_size;
 	u32 ahbcfg;

+ 15 - 1
drivers/usb/dwc2/hcd.c

@@ -121,7 +121,7 @@ static void dwc2_init_fs_ls_pclk_sel(struct dwc2_hsotg *hsotg)
 
 static int dwc2_fs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy)
 {
-	u32 usbcfg, i2cctl;
+	u32 usbcfg, ggpio, i2cctl;
 	int retval = 0;
 
 	/*
@@ -145,6 +145,19 @@ static int dwc2_fs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy)
 				return retval;
 			}
 		}
+
+		if (hsotg->params.activate_stm_fs_transceiver) {
+			ggpio = dwc2_readl(hsotg->regs + GGPIO);
+			if (!(ggpio & GGPIO_STM32_OTG_GCCFG_PWRDWN)) {
+				dev_dbg(hsotg->dev, "Activating transceiver\n");
+				/*
+				 * STM32F4x9 uses the GGPIO register as general
+				 * core configuration register.
+				 */
+				ggpio |= GGPIO_STM32_OTG_GCCFG_PWRDWN;
+				dwc2_writel(ggpio, hsotg->regs + GGPIO);
+			}
+		}
 	}
 
 	/*
@@ -3264,6 +3277,7 @@ static void dwc2_conn_id_status_change(struct work_struct *work)
 		dwc2_core_init(hsotg, false);
 		dwc2_enable_global_interrupts(hsotg);
 		spin_lock_irqsave(&hsotg->lock, flags);
+		dwc2_hsotg_disconnect(hsotg);
 		dwc2_hsotg_core_init_disconnected(hsotg, false);
 		spin_unlock_irqrestore(&hsotg->lock, flags);
 		dwc2_hsotg_core_connect(hsotg);

+ 2 - 0
drivers/usb/dwc2/hw.h

@@ -225,6 +225,8 @@
 
 #define GPVNDCTL			HSOTG_REG(0x0034)
 #define GGPIO				HSOTG_REG(0x0038)
+#define GGPIO_STM32_OTG_GCCFG_PWRDWN	BIT(16)
+
 #define GUID				HSOTG_REG(0x003c)
 #define GSNPSID				HSOTG_REG(0x0040)
 #define GHWCFG1				HSOTG_REG(0x0044)

+ 19 - 0
drivers/usb/dwc2/params.c

@@ -120,6 +120,22 @@ static void dwc2_set_amcc_params(struct dwc2_hsotg *hsotg)
 	p->ahbcfg = GAHBCFG_HBSTLEN_INCR16 << GAHBCFG_HBSTLEN_SHIFT;
 }
 
+static void dwc2_set_stm32f4x9_fsotg_params(struct dwc2_hsotg *hsotg)
+{
+	struct dwc2_core_params *p = &hsotg->params;
+
+	p->otg_cap = DWC2_CAP_PARAM_NO_HNP_SRP_CAPABLE;
+	p->speed = DWC2_SPEED_PARAM_FULL;
+	p->host_rx_fifo_size = 128;
+	p->host_nperio_tx_fifo_size = 96;
+	p->host_perio_tx_fifo_size = 96;
+	p->max_packet_count = 256;
+	p->phy_type = DWC2_PHY_TYPE_PARAM_FS;
+	p->i2c_enable = false;
+	p->uframe_sched = false;
+	p->activate_stm_fs_transceiver = true;
+}
+
 const struct of_device_id dwc2_of_match_table[] = {
 	{ .compatible = "brcm,bcm2835-usb", .data = dwc2_set_bcm_params },
 	{ .compatible = "hisilicon,hi6220-usb", .data = dwc2_set_his_params  },
@@ -133,6 +149,9 @@ const struct of_device_id dwc2_of_match_table[] = {
 	{ .compatible = "amlogic,meson-gxbb-usb",
 	  .data = dwc2_set_amlogic_params },
 	{ .compatible = "amcc,dwc-otg", .data = dwc2_set_amcc_params },
+	{ .compatible = "st,stm32f4x9-fsotg",
+	  .data = dwc2_set_stm32f4x9_fsotg_params },
+	{ .compatible = "st,stm32f4x9-hsotg" },
 	{},
 };
 MODULE_DEVICE_TABLE(of, dwc2_of_match_table);

+ 4 - 14
drivers/usb/dwc2/platform.c

@@ -214,20 +214,11 @@ static int dwc2_lowlevel_hw_init(struct dwc2_hsotg *hsotg)
 	hsotg->reset = devm_reset_control_get_optional(hsotg->dev, "dwc2");
 	if (IS_ERR(hsotg->reset)) {
 		ret = PTR_ERR(hsotg->reset);
-		switch (ret) {
-		case -ENOENT:
-		case -ENOTSUPP:
-			hsotg->reset = NULL;
-			break;
-		default:
-			dev_err(hsotg->dev, "error getting reset control %d\n",
-				ret);
-			return ret;
-		}
+		dev_err(hsotg->dev, "error getting reset control %d\n", ret);
+		return ret;
 	}
 
-	if (hsotg->reset)
-		reset_control_deassert(hsotg->reset);
+	reset_control_deassert(hsotg->reset);
 
 	/* Set default UTMI width */
 	hsotg->phyif = GUSBCFG_PHYIF16;
@@ -326,8 +317,7 @@ static int dwc2_driver_remove(struct platform_device *dev)
 	if (hsotg->ll_hw_enabled)
 		dwc2_lowlevel_hw_disable(hsotg);
 
-	if (hsotg->reset)
-		reset_control_assert(hsotg->reset);
+	reset_control_assert(hsotg->reset);
 
 	return 0;
 }

+ 2 - 1
drivers/usb/dwc3/Kconfig

@@ -41,6 +41,7 @@ config USB_DWC3_GADGET
 config USB_DWC3_DUAL_ROLE
 	bool "Dual Role mode"
 	depends on ((USB=y || USB=USB_DWC3) && (USB_GADGET=y || USB_GADGET=USB_DWC3))
+	depends on (EXTCON=y || EXTCON=USB_DWC3)
 	help
 	  This is the default mode of working of DWC3 controller where
 	  both host and gadget features are enabled.
@@ -70,7 +71,7 @@ config USB_DWC3_EXYNOS
 
 config USB_DWC3_PCI
 	tristate "PCIe-based Platforms"
-	depends on PCI && ACPI
+	depends on USB_PCI && ACPI
 	default USB_DWC3
 	help
 	  If you're using the DesignWare Core IP with a PCIe, please say

+ 4 - 0
drivers/usb/dwc3/Makefile

@@ -17,6 +17,10 @@ ifneq ($(filter y,$(CONFIG_USB_DWC3_GADGET) $(CONFIG_USB_DWC3_DUAL_ROLE)),)
 	dwc3-y				+= gadget.o ep0.o
 endif
 
+ifneq ($(CONFIG_USB_DWC3_DUAL_ROLE),)
+	dwc3-y				+= drd.o
+endif
+
 ifneq ($(CONFIG_USB_DWC3_ULPI),)
 	dwc3-y				+= ulpi.o
 endif

+ 80 - 29
drivers/usb/dwc3/core.c

@@ -100,7 +100,10 @@ static int dwc3_get_dr_mode(struct dwc3 *dwc)
 	return 0;
 }
 
-void dwc3_set_mode(struct dwc3 *dwc, u32 mode)
+static void dwc3_event_buffers_cleanup(struct dwc3 *dwc);
+static int dwc3_event_buffers_setup(struct dwc3 *dwc);
+
+static void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode)
 {
 	u32 reg;
 
@@ -110,6 +113,69 @@ void dwc3_set_mode(struct dwc3 *dwc, u32 mode)
 	dwc3_writel(dwc->regs, DWC3_GCTL, reg);
 }
 
+static void __dwc3_set_mode(struct work_struct *work)
+{
+	struct dwc3 *dwc = work_to_dwc(work);
+	unsigned long flags;
+	int ret;
+
+	if (!dwc->desired_dr_role)
+		return;
+
+	if (dwc->desired_dr_role == dwc->current_dr_role)
+		return;
+
+	if (dwc->dr_mode != USB_DR_MODE_OTG)
+		return;
+
+	switch (dwc->current_dr_role) {
+	case DWC3_GCTL_PRTCAP_HOST:
+		dwc3_host_exit(dwc);
+		break;
+	case DWC3_GCTL_PRTCAP_DEVICE:
+		dwc3_gadget_exit(dwc);
+		dwc3_event_buffers_cleanup(dwc);
+		break;
+	default:
+		break;
+	}
+
+	spin_lock_irqsave(&dwc->lock, flags);
+
+	dwc3_set_prtcap(dwc, dwc->desired_dr_role);
+
+	dwc->current_dr_role = dwc->desired_dr_role;
+
+	spin_unlock_irqrestore(&dwc->lock, flags);
+
+	switch (dwc->desired_dr_role) {
+	case DWC3_GCTL_PRTCAP_HOST:
+		ret = dwc3_host_init(dwc);
+		if (ret)
+			dev_err(dwc->dev, "failed to initialize host\n");
+		break;
+	case DWC3_GCTL_PRTCAP_DEVICE:
+		dwc3_event_buffers_setup(dwc);
+		ret = dwc3_gadget_init(dwc);
+		if (ret)
+			dev_err(dwc->dev, "failed to initialize peripheral\n");
+		break;
+	default:
+		break;
+	}
+}
+
+void dwc3_set_mode(struct dwc3 *dwc, u32 mode)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dwc->lock, flags);
+	dwc->desired_dr_role = mode;
+	spin_unlock_irqrestore(&dwc->lock, flags);
+
+	queue_work(system_power_efficient_wq, &dwc->drd_work);
+}
+
 u32 dwc3_core_fifo_space(struct dwc3_ep *dep, u8 type)
 {
 	struct dwc3		*dwc = dep->dwc;
@@ -397,8 +463,7 @@ static void dwc3_core_num_eps(struct dwc3 *dwc)
 {
 	struct dwc3_hwparams	*parms = &dwc->hwparams;
 
-	dwc->num_in_eps = DWC3_NUM_IN_EPS(parms);
-	dwc->num_out_eps = DWC3_NUM_EPS(parms) - dwc->num_in_eps;
+	dwc->num_eps = DWC3_NUM_EPS(parms);
 }
 
 static void dwc3_cache_hwparams(struct dwc3 *dwc)
@@ -431,6 +496,12 @@ static int dwc3_phy_setup(struct dwc3 *dwc)
 
 	reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0));
 
+	/*
+	 * Make sure UX_EXIT_PX is cleared as that causes issues with some
+	 * PHYs. Also, this bit is not supposed to be used in normal operation.
+	 */
+	reg &= ~DWC3_GUSB3PIPECTL_UX_EXIT_PX;
+
 	/*
 	 * Above 1.94a, it is recommended to set DWC3_GUSB3PIPECTL_SUSPHY
 	 * to '0' during coreConsultant configuration. So default value
@@ -714,21 +785,6 @@ static int dwc3_core_init(struct dwc3 *dwc)
 		goto err4;
 	}
 
-	switch (dwc->dr_mode) {
-	case USB_DR_MODE_PERIPHERAL:
-		dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
-		break;
-	case USB_DR_MODE_HOST:
-		dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST);
-		break;
-	case USB_DR_MODE_OTG:
-		dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG);
-		break;
-	default:
-		dev_warn(dwc->dev, "Unsupported mode %d\n", dwc->dr_mode);
-		break;
-	}
-
 	/*
 	 * ENDXFER polling is available on version 3.10a and later of
 	 * the DWC_usb3 controller. It is NOT available in the
@@ -846,6 +902,7 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
 
 	switch (dwc->dr_mode) {
 	case USB_DR_MODE_PERIPHERAL:
+		dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_DEVICE);
 		ret = dwc3_gadget_init(dwc);
 		if (ret) {
 			if (ret != -EPROBE_DEFER)
@@ -854,6 +911,7 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
 		}
 		break;
 	case USB_DR_MODE_HOST:
+		dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_HOST);
 		ret = dwc3_host_init(dwc);
 		if (ret) {
 			if (ret != -EPROBE_DEFER)
@@ -862,17 +920,11 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
 		}
 		break;
 	case USB_DR_MODE_OTG:
-		ret = dwc3_host_init(dwc);
-		if (ret) {
-			if (ret != -EPROBE_DEFER)
-				dev_err(dev, "failed to initialize host\n");
-			return ret;
-		}
-
-		ret = dwc3_gadget_init(dwc);
+		INIT_WORK(&dwc->drd_work, __dwc3_set_mode);
+		ret = dwc3_drd_init(dwc);
 		if (ret) {
 			if (ret != -EPROBE_DEFER)
-				dev_err(dev, "failed to initialize gadget\n");
+				dev_err(dev, "failed to initialize dual-role\n");
 			return ret;
 		}
 		break;
@@ -894,8 +946,7 @@ static void dwc3_core_exit_mode(struct dwc3 *dwc)
 		dwc3_host_exit(dwc);
 		break;
 	case USB_DR_MODE_OTG:
-		dwc3_host_exit(dwc);
-		dwc3_gadget_exit(dwc);
+		dwc3_drd_exit(dwc);
 		break;
 	default:
 		/* do nothing */

+ 136 - 125
drivers/usb/dwc3/core.h

@@ -23,10 +23,12 @@
 #include <linux/spinlock.h>
 #include <linux/ioport.h>
 #include <linux/list.h>
+#include <linux/bitops.h>
 #include <linux/dma-mapping.h>
 #include <linux/mm.h>
 #include <linux/debugfs.h>
 #include <linux/wait.h>
+#include <linux/workqueue.h>
 
 #include <linux/usb/ch9.h>
 #include <linux/usb/gadget.h>
@@ -39,9 +41,8 @@
 
 /* Global constants */
 #define DWC3_PULL_UP_TIMEOUT	500	/* ms */
-#define DWC3_ZLP_BUF_SIZE	1024	/* size of a superspeed bulk */
 #define DWC3_BOUNCE_SIZE	1024	/* size of a superspeed bulk */
-#define DWC3_EP0_BOUNCE_SIZE	512
+#define DWC3_EP0_SETUP_SIZE	512
 #define DWC3_ENDPOINTS_NUM	32
 #define DWC3_XHCI_RESOURCES_NUM	2
 
@@ -66,7 +67,7 @@
 #define DWC3_DEVICE_EVENT_OVERFLOW		11
 
 #define DWC3_GEVNTCOUNT_MASK	0xfffc
-#define DWC3_GEVNTCOUNT_EHB	(1 << 31)
+#define DWC3_GEVNTCOUNT_EHB	BIT(31)
 #define DWC3_GSNPSID_MASK	0xffff0000
 #define DWC3_GSNPSREV_MASK	0xffff
 
@@ -116,20 +117,20 @@
 #define DWC3_VER_NUMBER		0xc1a0
 #define DWC3_VER_TYPE		0xc1a4
 
-#define DWC3_GUSB2PHYCFG(n)	(0xc200 + (n * 0x04))
-#define DWC3_GUSB2I2CCTL(n)	(0xc240 + (n * 0x04))
+#define DWC3_GUSB2PHYCFG(n)	(0xc200 + ((n) * 0x04))
+#define DWC3_GUSB2I2CCTL(n)	(0xc240 + ((n) * 0x04))
 
-#define DWC3_GUSB2PHYACC(n)	(0xc280 + (n * 0x04))
+#define DWC3_GUSB2PHYACC(n)	(0xc280 + ((n) * 0x04))
 
-#define DWC3_GUSB3PIPECTL(n)	(0xc2c0 + (n * 0x04))
+#define DWC3_GUSB3PIPECTL(n)	(0xc2c0 + ((n) * 0x04))
 
-#define DWC3_GTXFIFOSIZ(n)	(0xc300 + (n * 0x04))
-#define DWC3_GRXFIFOSIZ(n)	(0xc380 + (n * 0x04))
+#define DWC3_GTXFIFOSIZ(n)	(0xc300 + ((n) * 0x04))
+#define DWC3_GRXFIFOSIZ(n)	(0xc380 + ((n) * 0x04))
 
-#define DWC3_GEVNTADRLO(n)	(0xc400 + (n * 0x10))
-#define DWC3_GEVNTADRHI(n)	(0xc404 + (n * 0x10))
-#define DWC3_GEVNTSIZ(n)	(0xc408 + (n * 0x10))
-#define DWC3_GEVNTCOUNT(n)	(0xc40c + (n * 0x10))
+#define DWC3_GEVNTADRLO(n)	(0xc400 + ((n) * 0x10))
+#define DWC3_GEVNTADRHI(n)	(0xc404 + ((n) * 0x10))
+#define DWC3_GEVNTSIZ(n)	(0xc408 + ((n) * 0x10))
+#define DWC3_GEVNTCOUNT(n)	(0xc40c + ((n) * 0x10))
 
 #define DWC3_GHWPARAMS8		0xc600
 #define DWC3_GFLADJ		0xc630
@@ -143,13 +144,13 @@
 #define DWC3_DGCMD		0xc714
 #define DWC3_DALEPENA		0xc720
 
-#define DWC3_DEP_BASE(n)	(0xc800 + (n * 0x10))
+#define DWC3_DEP_BASE(n)	(0xc800 + ((n) * 0x10))
 #define DWC3_DEPCMDPAR2		0x00
 #define DWC3_DEPCMDPAR1		0x04
 #define DWC3_DEPCMDPAR0		0x08
 #define DWC3_DEPCMD		0x0c
 
-#define DWC3_DEV_IMOD(n)	(0xca00 + (n * 0x4))
+#define DWC3_DEV_IMOD(n)	(0xca00 + ((n) * 0x4))
 
 /* OTG Registers */
 #define DWC3_OCFG		0xcc00
@@ -176,11 +177,11 @@
 /* Global RX Threshold Configuration Register */
 #define DWC3_GRXTHRCFG_MAXRXBURSTSIZE(n) (((n) & 0x1f) << 19)
 #define DWC3_GRXTHRCFG_RXPKTCNT(n) (((n) & 0xf) << 24)
-#define DWC3_GRXTHRCFG_PKTCNTSEL (1 << 29)
+#define DWC3_GRXTHRCFG_PKTCNTSEL BIT(29)
 
 /* Global Configuration Register */
 #define DWC3_GCTL_PWRDNSCALE(n)	((n) << 19)
-#define DWC3_GCTL_U2RSTECN	(1 << 16)
+#define DWC3_GCTL_U2RSTECN	BIT(16)
 #define DWC3_GCTL_RAMCLKSEL(x)	(((x) & DWC3_GCTL_CLK_MASK) << 6)
 #define DWC3_GCTL_CLK_BUS	(0)
 #define DWC3_GCTL_CLK_PIPE	(1)
@@ -193,24 +194,24 @@
 #define DWC3_GCTL_PRTCAP_DEVICE	2
 #define DWC3_GCTL_PRTCAP_OTG	3
 
-#define DWC3_GCTL_CORESOFTRESET		(1 << 11)
-#define DWC3_GCTL_SOFITPSYNC		(1 << 10)
+#define DWC3_GCTL_CORESOFTRESET		BIT(11)
+#define DWC3_GCTL_SOFITPSYNC		BIT(10)
 #define DWC3_GCTL_SCALEDOWN(n)		((n) << 4)
 #define DWC3_GCTL_SCALEDOWN_MASK	DWC3_GCTL_SCALEDOWN(3)
-#define DWC3_GCTL_DISSCRAMBLE		(1 << 3)
-#define DWC3_GCTL_U2EXIT_LFPS		(1 << 2)
-#define DWC3_GCTL_GBLHIBERNATIONEN	(1 << 1)
-#define DWC3_GCTL_DSBLCLKGTNG		(1 << 0)
+#define DWC3_GCTL_DISSCRAMBLE		BIT(3)
+#define DWC3_GCTL_U2EXIT_LFPS		BIT(2)
+#define DWC3_GCTL_GBLHIBERNATIONEN	BIT(1)
+#define DWC3_GCTL_DSBLCLKGTNG		BIT(0)
 
 /* Global User Control 1 Register */
-#define DWC3_GUCTL1_DEV_L1_EXIT_BY_HW	(1 << 24)
+#define DWC3_GUCTL1_DEV_L1_EXIT_BY_HW	BIT(24)
 
 /* Global USB2 PHY Configuration Register */
-#define DWC3_GUSB2PHYCFG_PHYSOFTRST	(1 << 31)
-#define DWC3_GUSB2PHYCFG_U2_FREECLK_EXISTS	(1 << 30)
-#define DWC3_GUSB2PHYCFG_SUSPHY		(1 << 6)
-#define DWC3_GUSB2PHYCFG_ULPI_UTMI	(1 << 4)
-#define DWC3_GUSB2PHYCFG_ENBLSLPM	(1 << 8)
+#define DWC3_GUSB2PHYCFG_PHYSOFTRST	BIT(31)
+#define DWC3_GUSB2PHYCFG_U2_FREECLK_EXISTS	BIT(30)
+#define DWC3_GUSB2PHYCFG_SUSPHY		BIT(6)
+#define DWC3_GUSB2PHYCFG_ULPI_UTMI	BIT(4)
+#define DWC3_GUSB2PHYCFG_ENBLSLPM	BIT(8)
 #define DWC3_GUSB2PHYCFG_PHYIF(n)	(n << 3)
 #define DWC3_GUSB2PHYCFG_PHYIF_MASK	DWC3_GUSB2PHYCFG_PHYIF(1)
 #define DWC3_GUSB2PHYCFG_USBTRDTIM(n)	(n << 10)
@@ -221,25 +222,26 @@
 #define UTMI_PHYIF_8_BIT		0
 
 /* Global USB2 PHY Vendor Control Register */
-#define DWC3_GUSB2PHYACC_NEWREGREQ	(1 << 25)
-#define DWC3_GUSB2PHYACC_BUSY		(1 << 23)
-#define DWC3_GUSB2PHYACC_WRITE		(1 << 22)
+#define DWC3_GUSB2PHYACC_NEWREGREQ	BIT(25)
+#define DWC3_GUSB2PHYACC_BUSY		BIT(23)
+#define DWC3_GUSB2PHYACC_WRITE		BIT(22)
 #define DWC3_GUSB2PHYACC_ADDR(n)	(n << 16)
 #define DWC3_GUSB2PHYACC_EXTEND_ADDR(n)	(n << 8)
 #define DWC3_GUSB2PHYACC_DATA(n)	(n & 0xff)
 
 /* Global USB3 PIPE Control Register */
-#define DWC3_GUSB3PIPECTL_PHYSOFTRST	(1 << 31)
-#define DWC3_GUSB3PIPECTL_U2SSINP3OK	(1 << 29)
-#define DWC3_GUSB3PIPECTL_DISRXDETINP3	(1 << 28)
-#define DWC3_GUSB3PIPECTL_REQP1P2P3	(1 << 24)
+#define DWC3_GUSB3PIPECTL_PHYSOFTRST	BIT(31)
+#define DWC3_GUSB3PIPECTL_U2SSINP3OK	BIT(29)
+#define DWC3_GUSB3PIPECTL_DISRXDETINP3	BIT(28)
+#define DWC3_GUSB3PIPECTL_UX_EXIT_PX	BIT(27)
+#define DWC3_GUSB3PIPECTL_REQP1P2P3	BIT(24)
 #define DWC3_GUSB3PIPECTL_DEP1P2P3(n)	((n) << 19)
 #define DWC3_GUSB3PIPECTL_DEP1P2P3_MASK	DWC3_GUSB3PIPECTL_DEP1P2P3(7)
 #define DWC3_GUSB3PIPECTL_DEP1P2P3_EN	DWC3_GUSB3PIPECTL_DEP1P2P3(1)
-#define DWC3_GUSB3PIPECTL_DEPOCHANGE	(1 << 18)
-#define DWC3_GUSB3PIPECTL_SUSPHY	(1 << 17)
-#define DWC3_GUSB3PIPECTL_LFPSFILT	(1 << 9)
-#define DWC3_GUSB3PIPECTL_RX_DETOPOLL	(1 << 8)
+#define DWC3_GUSB3PIPECTL_DEPOCHANGE	BIT(18)
+#define DWC3_GUSB3PIPECTL_SUSPHY	BIT(17)
+#define DWC3_GUSB3PIPECTL_LFPSFILT	BIT(9)
+#define DWC3_GUSB3PIPECTL_RX_DETOPOLL	BIT(8)
 #define DWC3_GUSB3PIPECTL_TX_DEEPH_MASK	DWC3_GUSB3PIPECTL_TX_DEEPH(3)
 #define DWC3_GUSB3PIPECTL_TX_DEEPH(n)	((n) << 1)
 
@@ -248,7 +250,7 @@
 #define DWC3_GTXFIFOSIZ_TXFSTADDR(n)	((n) & 0xffff0000)
 
 /* Global Event Size Registers */
-#define DWC3_GEVNTSIZ_INTMASK		(1 << 31)
+#define DWC3_GEVNTSIZ_INTMASK		BIT(31)
 #define DWC3_GEVNTSIZ_SIZE(n)		((n) & 0xffff)
 
 /* Global HWPARAMS0 Register */
@@ -289,18 +291,18 @@
 #define DWC3_MAX_HIBER_SCRATCHBUFS		15
 
 /* Global HWPARAMS6 Register */
-#define DWC3_GHWPARAMS6_EN_FPGA			(1 << 7)
+#define DWC3_GHWPARAMS6_EN_FPGA			BIT(7)
 
 /* Global HWPARAMS7 Register */
 #define DWC3_GHWPARAMS7_RAM1_DEPTH(n)	((n) & 0xffff)
 #define DWC3_GHWPARAMS7_RAM2_DEPTH(n)	(((n) >> 16) & 0xffff)
 
 /* Global Frame Length Adjustment Register */
-#define DWC3_GFLADJ_30MHZ_SDBND_SEL		(1 << 7)
+#define DWC3_GFLADJ_30MHZ_SDBND_SEL		BIT(7)
 #define DWC3_GFLADJ_30MHZ_MASK			0x3f
 
 /* Global User Control Register 2 */
-#define DWC3_GUCTL2_RST_ACTBITLATER		(1 << 14)
+#define DWC3_GUCTL2_RST_ACTBITLATER		BIT(14)
 
 /* Device Configuration Register */
 #define DWC3_DCFG_DEVADDR(addr)	((addr) << 3)
@@ -310,23 +312,23 @@
 #define DWC3_DCFG_SUPERSPEED_PLUS (5 << 0)  /* DWC_usb31 only */
 #define DWC3_DCFG_SUPERSPEED	(4 << 0)
 #define DWC3_DCFG_HIGHSPEED	(0 << 0)
-#define DWC3_DCFG_FULLSPEED	(1 << 0)
+#define DWC3_DCFG_FULLSPEED	BIT(0)
 #define DWC3_DCFG_LOWSPEED	(2 << 0)
 
 #define DWC3_DCFG_NUMP_SHIFT	17
 #define DWC3_DCFG_NUMP(n)	(((n) >> DWC3_DCFG_NUMP_SHIFT) & 0x1f)
 #define DWC3_DCFG_NUMP_MASK	(0x1f << DWC3_DCFG_NUMP_SHIFT)
-#define DWC3_DCFG_LPM_CAP	(1 << 22)
+#define DWC3_DCFG_LPM_CAP	BIT(22)
 
 /* Device Control Register */
-#define DWC3_DCTL_RUN_STOP	(1 << 31)
-#define DWC3_DCTL_CSFTRST	(1 << 30)
-#define DWC3_DCTL_LSFTRST	(1 << 29)
+#define DWC3_DCTL_RUN_STOP	BIT(31)
+#define DWC3_DCTL_CSFTRST	BIT(30)
+#define DWC3_DCTL_LSFTRST	BIT(29)
 
 #define DWC3_DCTL_HIRD_THRES_MASK	(0x1f << 24)
 #define DWC3_DCTL_HIRD_THRES(n)	((n) << 24)
 
-#define DWC3_DCTL_APPL1RES	(1 << 23)
+#define DWC3_DCTL_APPL1RES	BIT(23)
 
 /* These apply for core versions 1.87a and earlier */
 #define DWC3_DCTL_TRGTULST_MASK		(0x0f << 17)
@@ -341,15 +343,15 @@
 #define DWC3_DCTL_LPM_ERRATA_MASK	DWC3_DCTL_LPM_ERRATA(0xf)
 #define DWC3_DCTL_LPM_ERRATA(n)		((n) << 20)
 
-#define DWC3_DCTL_KEEP_CONNECT		(1 << 19)
-#define DWC3_DCTL_L1_HIBER_EN		(1 << 18)
-#define DWC3_DCTL_CRS			(1 << 17)
-#define DWC3_DCTL_CSS			(1 << 16)
+#define DWC3_DCTL_KEEP_CONNECT		BIT(19)
+#define DWC3_DCTL_L1_HIBER_EN		BIT(18)
+#define DWC3_DCTL_CRS			BIT(17)
+#define DWC3_DCTL_CSS			BIT(16)
 
-#define DWC3_DCTL_INITU2ENA		(1 << 12)
-#define DWC3_DCTL_ACCEPTU2ENA		(1 << 11)
-#define DWC3_DCTL_INITU1ENA		(1 << 10)
-#define DWC3_DCTL_ACCEPTU1ENA		(1 << 9)
+#define DWC3_DCTL_INITU2ENA		BIT(12)
+#define DWC3_DCTL_ACCEPTU2ENA		BIT(11)
+#define DWC3_DCTL_INITU1ENA		BIT(10)
+#define DWC3_DCTL_ACCEPTU1ENA		BIT(9)
 #define DWC3_DCTL_TSTCTRL_MASK		(0xf << 1)
 
 #define DWC3_DCTL_ULSTCHNGREQ_MASK	(0x0f << 5)
@@ -364,36 +366,36 @@
 #define DWC3_DCTL_ULSTCHNG_LOOPBACK	(DWC3_DCTL_ULSTCHNGREQ(11))
 
 /* Device Event Enable Register */
-#define DWC3_DEVTEN_VNDRDEVTSTRCVEDEN	(1 << 12)
-#define DWC3_DEVTEN_EVNTOVERFLOWEN	(1 << 11)
-#define DWC3_DEVTEN_CMDCMPLTEN		(1 << 10)
-#define DWC3_DEVTEN_ERRTICERREN		(1 << 9)
-#define DWC3_DEVTEN_SOFEN		(1 << 7)
-#define DWC3_DEVTEN_EOPFEN		(1 << 6)
-#define DWC3_DEVTEN_HIBERNATIONREQEVTEN	(1 << 5)
-#define DWC3_DEVTEN_WKUPEVTEN		(1 << 4)
-#define DWC3_DEVTEN_ULSTCNGEN		(1 << 3)
-#define DWC3_DEVTEN_CONNECTDONEEN	(1 << 2)
-#define DWC3_DEVTEN_USBRSTEN		(1 << 1)
-#define DWC3_DEVTEN_DISCONNEVTEN	(1 << 0)
+#define DWC3_DEVTEN_VNDRDEVTSTRCVEDEN	BIT(12)
+#define DWC3_DEVTEN_EVNTOVERFLOWEN	BIT(11)
+#define DWC3_DEVTEN_CMDCMPLTEN		BIT(10)
+#define DWC3_DEVTEN_ERRTICERREN		BIT(9)
+#define DWC3_DEVTEN_SOFEN		BIT(7)
+#define DWC3_DEVTEN_EOPFEN		BIT(6)
+#define DWC3_DEVTEN_HIBERNATIONREQEVTEN	BIT(5)
+#define DWC3_DEVTEN_WKUPEVTEN		BIT(4)
+#define DWC3_DEVTEN_ULSTCNGEN		BIT(3)
+#define DWC3_DEVTEN_CONNECTDONEEN	BIT(2)
+#define DWC3_DEVTEN_USBRSTEN		BIT(1)
+#define DWC3_DEVTEN_DISCONNEVTEN	BIT(0)
 
 /* Device Status Register */
-#define DWC3_DSTS_DCNRD			(1 << 29)
+#define DWC3_DSTS_DCNRD			BIT(29)
 
 /* This applies for core versions 1.87a and earlier */
-#define DWC3_DSTS_PWRUPREQ		(1 << 24)
+#define DWC3_DSTS_PWRUPREQ		BIT(24)
 
 /* These apply for core versions 1.94a and later */
-#define DWC3_DSTS_RSS			(1 << 25)
-#define DWC3_DSTS_SSS			(1 << 24)
+#define DWC3_DSTS_RSS			BIT(25)
+#define DWC3_DSTS_SSS			BIT(24)
 
-#define DWC3_DSTS_COREIDLE		(1 << 23)
-#define DWC3_DSTS_DEVCTRLHLT		(1 << 22)
+#define DWC3_DSTS_COREIDLE		BIT(23)
+#define DWC3_DSTS_DEVCTRLHLT		BIT(22)
 
 #define DWC3_DSTS_USBLNKST_MASK		(0x0f << 18)
 #define DWC3_DSTS_USBLNKST(n)		(((n) & DWC3_DSTS_USBLNKST_MASK) >> 18)
 
-#define DWC3_DSTS_RXFIFOEMPTY		(1 << 17)
+#define DWC3_DSTS_RXFIFOEMPTY		BIT(17)
 
 #define DWC3_DSTS_SOFFN_MASK		(0x3fff << 3)
 #define DWC3_DSTS_SOFFN(n)		(((n) & DWC3_DSTS_SOFFN_MASK) >> 3)
@@ -403,7 +405,7 @@
 #define DWC3_DSTS_SUPERSPEED_PLUS	(5 << 0) /* DWC_usb31 only */
 #define DWC3_DSTS_SUPERSPEED		(4 << 0)
 #define DWC3_DSTS_HIGHSPEED		(0 << 0)
-#define DWC3_DSTS_FULLSPEED		(1 << 0)
+#define DWC3_DSTS_FULLSPEED		BIT(0)
 #define DWC3_DSTS_LOWSPEED		(2 << 0)
 
 /* Device Generic Command Register */
@@ -421,26 +423,26 @@
 #define DWC3_DGCMD_RUN_SOC_BUS_LOOPBACK	0x10
 
 #define DWC3_DGCMD_STATUS(n)		(((n) >> 12) & 0x0F)
-#define DWC3_DGCMD_CMDACT		(1 << 10)
-#define DWC3_DGCMD_CMDIOC		(1 << 8)
+#define DWC3_DGCMD_CMDACT		BIT(10)
+#define DWC3_DGCMD_CMDIOC		BIT(8)
 
 /* Device Generic Command Parameter Register */
-#define DWC3_DGCMDPAR_FORCE_LINKPM_ACCEPT	(1 << 0)
+#define DWC3_DGCMDPAR_FORCE_LINKPM_ACCEPT	BIT(0)
 #define DWC3_DGCMDPAR_FIFO_NUM(n)		((n) << 0)
 #define DWC3_DGCMDPAR_RX_FIFO			(0 << 5)
-#define DWC3_DGCMDPAR_TX_FIFO			(1 << 5)
+#define DWC3_DGCMDPAR_TX_FIFO			BIT(5)
 #define DWC3_DGCMDPAR_LOOPBACK_DIS		(0 << 0)
-#define DWC3_DGCMDPAR_LOOPBACK_ENA		(1 << 0)
+#define DWC3_DGCMDPAR_LOOPBACK_ENA		BIT(0)
 
 /* Device Endpoint Command Register */
 #define DWC3_DEPCMD_PARAM_SHIFT		16
 #define DWC3_DEPCMD_PARAM(x)		((x) << DWC3_DEPCMD_PARAM_SHIFT)
 #define DWC3_DEPCMD_GET_RSC_IDX(x)	(((x) >> DWC3_DEPCMD_PARAM_SHIFT) & 0x7f)
 #define DWC3_DEPCMD_STATUS(x)		(((x) >> 12) & 0x0F)
-#define DWC3_DEPCMD_HIPRI_FORCERM	(1 << 11)
-#define DWC3_DEPCMD_CLEARPENDIN		(1 << 11)
-#define DWC3_DEPCMD_CMDACT		(1 << 10)
-#define DWC3_DEPCMD_CMDIOC		(1 << 8)
+#define DWC3_DEPCMD_HIPRI_FORCERM	BIT(11)
+#define DWC3_DEPCMD_CLEARPENDIN		BIT(11)
+#define DWC3_DEPCMD_CMDACT		BIT(10)
+#define DWC3_DEPCMD_CMDIOC		BIT(8)
 
 #define DWC3_DEPCMD_DEPSTARTCFG		(0x09 << 0)
 #define DWC3_DEPCMD_ENDTRANSFER		(0x08 << 0)
@@ -458,7 +460,7 @@
 #define DWC3_DEPCMD_CMD(x)		((x) & 0xf)
 
 /* The EP number goes 0..31 so ep0 is always out and ep1 is always in */
-#define DWC3_DALEPENA_EP(n)		(1 << n)
+#define DWC3_DALEPENA_EP(n)		BIT(n)
 
 #define DWC3_DEPCMD_TYPE_CONTROL	0
 #define DWC3_DEPCMD_TYPE_ISOC		1
@@ -500,8 +502,8 @@ struct dwc3_event_buffer {
 	struct dwc3		*dwc;
 };
 
-#define DWC3_EP_FLAG_STALLED	(1 << 0)
-#define DWC3_EP_FLAG_WEDGED	(1 << 1)
+#define DWC3_EP_FLAG_STALLED	BIT(0)
+#define DWC3_EP_FLAG_WEDGED	BIT(1)
 
 #define DWC3_EP_DIRECTION_TX	true
 #define DWC3_EP_DIRECTION_RX	false
@@ -550,17 +552,17 @@ struct dwc3_ep {
 
 	u32			saved_state;
 	unsigned		flags;
-#define DWC3_EP_ENABLED		(1 << 0)
-#define DWC3_EP_STALL		(1 << 1)
-#define DWC3_EP_WEDGE		(1 << 2)
-#define DWC3_EP_BUSY		(1 << 4)
-#define DWC3_EP_PENDING_REQUEST	(1 << 5)
-#define DWC3_EP_MISSED_ISOC	(1 << 6)
-#define DWC3_EP_END_TRANSFER_PENDING	(1 << 7)
-#define DWC3_EP_TRANSFER_STARTED (1 << 8)
+#define DWC3_EP_ENABLED		BIT(0)
+#define DWC3_EP_STALL		BIT(1)
+#define DWC3_EP_WEDGE		BIT(2)
+#define DWC3_EP_BUSY		BIT(4)
+#define DWC3_EP_PENDING_REQUEST	BIT(5)
+#define DWC3_EP_MISSED_ISOC	BIT(6)
+#define DWC3_EP_END_TRANSFER_PENDING	BIT(7)
+#define DWC3_EP_TRANSFER_STARTED BIT(8)
 
 	/* This last one is specific to EP0 */
-#define DWC3_EP0_DIR_IN		(1 << 31)
+#define DWC3_EP0_DIR_IN		BIT(31)
 
 	/*
 	 * IMPORTANT: we *know* we have 256 TRBs in our @trb_pool, so we will
@@ -638,13 +640,13 @@ enum dwc3_link_state {
 #define DWC3_TRB_STS_XFER_IN_PROG	4
 
 /* TRB Control */
-#define DWC3_TRB_CTRL_HWO		(1 << 0)
-#define DWC3_TRB_CTRL_LST		(1 << 1)
-#define DWC3_TRB_CTRL_CHN		(1 << 2)
-#define DWC3_TRB_CTRL_CSP		(1 << 3)
+#define DWC3_TRB_CTRL_HWO		BIT(0)
+#define DWC3_TRB_CTRL_LST		BIT(1)
+#define DWC3_TRB_CTRL_CHN		BIT(2)
+#define DWC3_TRB_CTRL_CSP		BIT(3)
 #define DWC3_TRB_CTRL_TRBCTL(n)		(((n) & 0x3f) << 4)
-#define DWC3_TRB_CTRL_ISP_IMI		(1 << 10)
-#define DWC3_TRB_CTRL_IOC		(1 << 11)
+#define DWC3_TRB_CTRL_ISP_IMI		BIT(10)
+#define DWC3_TRB_CTRL_IOC		BIT(11)
 #define DWC3_TRB_CTRL_SID_SOFN(n)	(((n) & 0xffff) << 14)
 
 #define DWC3_TRBCTL_TYPE(n)		((n) & (0x3f << 4))
@@ -746,6 +748,7 @@ struct dwc3_request {
 	unsigned		direction:1;
 	unsigned		mapped:1;
 	unsigned		started:1;
+	unsigned		zero:1;
 };
 
 /*
@@ -758,15 +761,11 @@ struct dwc3_scratchpad_array {
 
 /**
  * struct dwc3 - representation of our controller
- * @ctrl_req: usb control request which is used for ep0
+ * @drd_work - workqueue used for role swapping
  * @ep0_trb: trb which is used for the ctrl_req
- * @ep0_bounce: bounce buffer for ep0
- * @zlp_buf: used when request->zero is set
  * @setup_buf: used while precessing STD USB requests
- * @ctrl_req_addr: dma address of ctrl_req
  * @ep0_trb: dma address of ep0_trb
  * @ep0_usb_req: dummy req used while handling STD USB requests
- * @ep0_bounce_addr: dma address of ep0_bounce
  * @scratch_addr: dma address of scratchbuf
  * @ep0_in_setup: one control transfer is completed and enter setup phase
  * @lock: for synchronizing
@@ -784,6 +783,10 @@ struct dwc3_scratchpad_array {
  * @maximum_speed: maximum speed requested (mainly for testing purposes)
  * @revision: revision register contents
  * @dr_mode: requested mode of operation
+ * @current_dr_role: current role of operation when in dual-role mode
+ * @desired_dr_role: desired role of operation when in dual-role mode
+ * @edev: extcon handle
+ * @edev_nb: extcon notifier
  * @hsphy_mode: UTMI phy mode, one of following:
  *		- USBPHY_INTERFACE_MODE_UTMI
  *		- USBPHY_INTERFACE_MODE_UTMIW
@@ -799,8 +802,7 @@ struct dwc3_scratchpad_array {
  * @u2pel: parameter from Set SEL request.
  * @u1sel: parameter from Set SEL request.
  * @u1pel: parameter from Set SEL request.
- * @num_out_eps: number of out endpoints
- * @num_in_eps: number of in endpoints
+ * @num_eps: number of endpoints
  * @ep0_next_event: hold the next expected event
  * @ep0state: state of endpoint zero
  * @link_state: link state
@@ -858,17 +860,13 @@ struct dwc3_scratchpad_array {
  *                 increments or 0 to disable.
  */
 struct dwc3 {
-	struct usb_ctrlrequest	*ctrl_req;
+	struct work_struct	drd_work;
 	struct dwc3_trb		*ep0_trb;
 	void			*bounce;
-	void			*ep0_bounce;
-	void			*zlp_buf;
 	void			*scratchbuf;
 	u8			*setup_buf;
-	dma_addr_t		ctrl_req_addr;
 	dma_addr_t		ep0_trb_addr;
 	dma_addr_t		bounce_addr;
-	dma_addr_t		ep0_bounce_addr;
 	dma_addr_t		scratch_addr;
 	struct dwc3_request	ep0_usb_req;
 	struct completion	ep0_in_setup;
@@ -900,6 +898,10 @@ struct dwc3 {
 	size_t			regs_size;
 
 	enum usb_dr_mode	dr_mode;
+	u32			current_dr_role;
+	u32			desired_dr_role;
+	struct extcon_dev	*edev;
+	struct notifier_block	edev_nb;
 	enum usb_phy_interface	hsphy_mode;
 
 	u32			fladj;
@@ -960,8 +962,7 @@ struct dwc3 {
 
 	u8			speed;
 
-	u8			num_out_eps;
-	u8			num_in_eps;
+	u8			num_eps;
 
 	struct dwc3_hwparams	hwparams;
 	struct dentry		*root;
@@ -1010,7 +1011,7 @@ struct dwc3 {
 	u16			imod_interval;
 };
 
-/* -------------------------------------------------------------------------- */
+#define work_to_dwc(w)		(container_of((w), struct dwc3, drd_work))
 
 /* -------------------------------------------------------------------------- */
 
@@ -1054,13 +1055,13 @@ struct dwc3_event_depevt {
 	u32	status:4;
 
 /* Within XferNotReady */
-#define DEPEVT_STATUS_TRANSFER_ACTIVE	(1 << 3)
+#define DEPEVT_STATUS_TRANSFER_ACTIVE	BIT(3)
 
 /* Within XferComplete */
-#define DEPEVT_STATUS_BUSERR	(1 << 0)
-#define DEPEVT_STATUS_SHORT	(1 << 1)
-#define DEPEVT_STATUS_IOC	(1 << 2)
-#define DEPEVT_STATUS_LST	(1 << 3)
+#define DEPEVT_STATUS_BUSERR	BIT(0)
+#define DEPEVT_STATUS_SHORT	BIT(1)
+#define DEPEVT_STATUS_IOC	BIT(2)
+#define DEPEVT_STATUS_LST	BIT(3)
 
 /* Stream event only */
 #define DEPEVT_STREAMEVT_FOUND		1
@@ -1221,6 +1222,16 @@ static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc,
 { return 0; }
 #endif
 
+#if IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
+int dwc3_drd_init(struct dwc3 *dwc);
+void dwc3_drd_exit(struct dwc3 *dwc);
+#else
+static inline int dwc3_drd_init(struct dwc3 *dwc)
+{ return 0; }
+static inline void dwc3_drd_exit(struct dwc3 *dwc)
+{ }
+#endif
+
 /* power management interface */
 #if !IS_ENABLED(CONFIG_USB_DWC3_HOST)
 int dwc3_gadget_suspend(struct dwc3 *dwc);

+ 28 - 0
drivers/usb/dwc3/debug.h

@@ -124,6 +124,34 @@ dwc3_gadget_link_string(enum dwc3_link_state link_state)
 	}
 }
 
+/**
+ * dwc3_trb_type_string - returns TRB type as a string
+ * @type: the type of the TRB
+ */
+static inline const char *dwc3_trb_type_string(unsigned int type)
+{
+	switch (type) {
+	case DWC3_TRBCTL_NORMAL:
+		return "normal";
+	case DWC3_TRBCTL_CONTROL_SETUP:
+		return "setup";
+	case DWC3_TRBCTL_CONTROL_STATUS2:
+		return "status2";
+	case DWC3_TRBCTL_CONTROL_STATUS3:
+		return "status3";
+	case DWC3_TRBCTL_CONTROL_DATA:
+		return "data";
+	case DWC3_TRBCTL_ISOCHRONOUS_FIRST:
+		return "isoc-first";
+	case DWC3_TRBCTL_ISOCHRONOUS:
+		return "isoc";
+	case DWC3_TRBCTL_LINK_TRB:
+		return "link";
+	default:
+		return "UNKNOWN";
+	}
+}
+
 static inline const char *dwc3_ep0_state_string(enum dwc3_ep0_state state)
 {
 	switch (state) {

+ 11 - 94
drivers/usb/dwc3/debugfs.c

@@ -300,7 +300,7 @@ static int dwc3_mode_show(struct seq_file *s, void *unused)
 		seq_printf(s, "device\n");
 		break;
 	case DWC3_GCTL_PRTCAP_OTG:
-		seq_printf(s, "OTG\n");
+		seq_printf(s, "otg\n");
 		break;
 	default:
 		seq_printf(s, "UNKNOWN %08x\n", DWC3_GCTL_PRTCAP(reg));
@@ -319,7 +319,6 @@ static ssize_t dwc3_mode_write(struct file *file,
 {
 	struct seq_file		*s = file->private_data;
 	struct dwc3		*dwc = s->private;
-	unsigned long		flags;
 	u32			mode = 0;
 	char			buf[32];
 
@@ -327,19 +326,16 @@ static ssize_t dwc3_mode_write(struct file *file,
 		return -EFAULT;
 
 	if (!strncmp(buf, "host", 4))
-		mode |= DWC3_GCTL_PRTCAP_HOST;
+		mode = DWC3_GCTL_PRTCAP_HOST;
 
 	if (!strncmp(buf, "device", 6))
-		mode |= DWC3_GCTL_PRTCAP_DEVICE;
+		mode = DWC3_GCTL_PRTCAP_DEVICE;
 
 	if (!strncmp(buf, "otg", 3))
-		mode |= DWC3_GCTL_PRTCAP_OTG;
+		mode = DWC3_GCTL_PRTCAP_OTG;
+
+	dwc3_set_mode(dwc, mode);
 
-	if (mode) {
-		spin_lock_irqsave(&dwc->lock, flags);
-		dwc3_set_mode(dwc, mode);
-		spin_unlock_irqrestore(&dwc->lock, flags);
-	}
 	return count;
 }
 
@@ -446,52 +442,7 @@ static int dwc3_link_state_show(struct seq_file *s, void *unused)
 	state = DWC3_DSTS_USBLNKST(reg);
 	spin_unlock_irqrestore(&dwc->lock, flags);
 
-	switch (state) {
-	case DWC3_LINK_STATE_U0:
-		seq_printf(s, "U0\n");
-		break;
-	case DWC3_LINK_STATE_U1:
-		seq_printf(s, "U1\n");
-		break;
-	case DWC3_LINK_STATE_U2:
-		seq_printf(s, "U2\n");
-		break;
-	case DWC3_LINK_STATE_U3:
-		seq_printf(s, "U3\n");
-		break;
-	case DWC3_LINK_STATE_SS_DIS:
-		seq_printf(s, "SS.Disabled\n");
-		break;
-	case DWC3_LINK_STATE_RX_DET:
-		seq_printf(s, "Rx.Detect\n");
-		break;
-	case DWC3_LINK_STATE_SS_INACT:
-		seq_printf(s, "SS.Inactive\n");
-		break;
-	case DWC3_LINK_STATE_POLL:
-		seq_printf(s, "Poll\n");
-		break;
-	case DWC3_LINK_STATE_RECOV:
-		seq_printf(s, "Recovery\n");
-		break;
-	case DWC3_LINK_STATE_HRESET:
-		seq_printf(s, "HRESET\n");
-		break;
-	case DWC3_LINK_STATE_CMPLY:
-		seq_printf(s, "Compliance\n");
-		break;
-	case DWC3_LINK_STATE_LPBK:
-		seq_printf(s, "Loopback\n");
-		break;
-	case DWC3_LINK_STATE_RESET:
-		seq_printf(s, "Reset\n");
-		break;
-	case DWC3_LINK_STATE_RESUME:
-		seq_printf(s, "Resume\n");
-		break;
-	default:
-		seq_printf(s, "UNKNOWN %d\n", state);
-	}
+	seq_printf(s, "%s\n", dwc3_gadget_link_string(state));
 
 	return 0;
 }
@@ -689,30 +640,6 @@ out:
 	return 0;
 }
 
-static inline const char *dwc3_trb_type_string(struct dwc3_trb *trb)
-{
-	switch (DWC3_TRBCTL_TYPE(trb->ctrl)) {
-	case DWC3_TRBCTL_NORMAL:
-		return "normal";
-	case DWC3_TRBCTL_CONTROL_SETUP:
-		return "control-setup";
-	case DWC3_TRBCTL_CONTROL_STATUS2:
-		return "control-status2";
-	case DWC3_TRBCTL_CONTROL_STATUS3:
-		return "control-status3";
-	case DWC3_TRBCTL_CONTROL_DATA:
-		return "control-data";
-	case DWC3_TRBCTL_ISOCHRONOUS_FIRST:
-		return "isoc-first";
-	case DWC3_TRBCTL_ISOCHRONOUS:
-		return "isoc";
-	case DWC3_TRBCTL_LINK_TRB:
-		return "link";
-	default:
-		return "UNKNOWN";
-	}
-}
-
 static int dwc3_ep_trb_ring_show(struct seq_file *s, void *unused)
 {
 	struct dwc3_ep		*dep = s->private;
@@ -733,10 +660,11 @@ static int dwc3_ep_trb_ring_show(struct seq_file *s, void *unused)
 
 	for (i = 0; i < DWC3_TRB_NUM; i++) {
 		struct dwc3_trb *trb = &dep->trb_pool[i];
+		unsigned int type = DWC3_TRBCTL_TYPE(trb->ctrl);
 
 		seq_printf(s, "%08x%08x,%d,%s,%d,%d,%d,%d,%d,%d\n",
 				trb->bph, trb->bpl, trb->size,
-				dwc3_trb_type_string(trb),
+				dwc3_trb_type_string(type),
 				!!(trb->ctrl & DWC3_TRB_CTRL_IOC),
 				!!(trb->ctrl & DWC3_TRB_CTRL_ISP_IMI),
 				!!(trb->ctrl & DWC3_TRB_CTRL_CSP),
@@ -822,19 +750,8 @@ static void dwc3_debugfs_create_endpoint_dirs(struct dwc3 *dwc,
 {
 	int			i;
 
-	for (i = 0; i < dwc->num_in_eps; i++) {
-		u8		epnum = (i << 1) | 1;
-		struct dwc3_ep	*dep = dwc->eps[epnum];
-
-		if (!dep)
-			continue;
-
-		dwc3_debugfs_create_endpoint_dir(dep, parent);
-	}
-
-	for (i = 0; i < dwc->num_out_eps; i++) {
-		u8		epnum = (i << 1);
-		struct dwc3_ep	*dep = dwc->eps[epnum];
+	for (i = 0; i < dwc->num_eps; i++) {
+		struct dwc3_ep	*dep = dwc->eps[i];
 
 		if (!dep)
 			continue;

+ 85 - 0
drivers/usb/dwc3/drd.c

@@ -0,0 +1,85 @@
+/**
+ * drd.c - DesignWare USB3 DRD Controller Dual-role support
+ *
+ * Copyright (C) 2017 Texas Instruments Incorporated - http://www.ti.com
+ *
+ * Authors: Roger Quadros <rogerq@ti.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2  of
+ * the License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/extcon.h>
+
+#include "debug.h"
+#include "core.h"
+#include "gadget.h"
+
+static void dwc3_drd_update(struct dwc3 *dwc)
+{
+	int id;
+
+	id = extcon_get_state(dwc->edev, EXTCON_USB_HOST);
+	if (id < 0)
+		id = 0;
+
+	dwc3_set_mode(dwc, id ?
+		      DWC3_GCTL_PRTCAP_HOST :
+		      DWC3_GCTL_PRTCAP_DEVICE);
+}
+
+static int dwc3_drd_notifier(struct notifier_block *nb,
+			     unsigned long event, void *ptr)
+{
+	struct dwc3 *dwc = container_of(nb, struct dwc3, edev_nb);
+
+	dwc3_set_mode(dwc, event ?
+		      DWC3_GCTL_PRTCAP_HOST :
+		      DWC3_GCTL_PRTCAP_DEVICE);
+
+	return NOTIFY_DONE;
+}
+
+int dwc3_drd_init(struct dwc3 *dwc)
+{
+	int ret;
+
+	if (dwc->dev->of_node) {
+		if (of_property_read_bool(dwc->dev->of_node, "extcon"))
+			dwc->edev = extcon_get_edev_by_phandle(dwc->dev, 0);
+
+		if (IS_ERR(dwc->edev))
+			return PTR_ERR(dwc->edev);
+
+		dwc->edev_nb.notifier_call = dwc3_drd_notifier;
+		ret = extcon_register_notifier(dwc->edev, EXTCON_USB_HOST,
+					       &dwc->edev_nb);
+		if (ret < 0) {
+			dev_err(dwc->dev, "couldn't register cable notifier\n");
+			return ret;
+		}
+	}
+
+	dwc3_drd_update(dwc);
+
+	return 0;
+}
+
+void dwc3_drd_exit(struct dwc3 *dwc)
+{
+	extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST,
+				   &dwc->edev_nb);
+
+	dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
+	flush_work(&dwc->drd_work);
+	dwc3_gadget_exit(dwc);
+}

+ 11 - 11
drivers/usb/dwc3/dwc3-exynos.c

@@ -147,53 +147,53 @@ static int dwc3_exynos_probe(struct platform_device *pdev)
 	exynos->vdd33 = devm_regulator_get(dev, "vdd33");
 	if (IS_ERR(exynos->vdd33)) {
 		ret = PTR_ERR(exynos->vdd33);
-		goto err2;
+		goto vdd33_err;
 	}
 	ret = regulator_enable(exynos->vdd33);
 	if (ret) {
 		dev_err(dev, "Failed to enable VDD33 supply\n");
-		goto err2;
+		goto vdd33_err;
 	}
 
 	exynos->vdd10 = devm_regulator_get(dev, "vdd10");
 	if (IS_ERR(exynos->vdd10)) {
 		ret = PTR_ERR(exynos->vdd10);
-		goto err3;
+		goto vdd10_err;
 	}
 	ret = regulator_enable(exynos->vdd10);
 	if (ret) {
 		dev_err(dev, "Failed to enable VDD10 supply\n");
-		goto err3;
+		goto vdd10_err;
 	}
 
 	ret = dwc3_exynos_register_phys(exynos);
 	if (ret) {
 		dev_err(dev, "couldn't register PHYs\n");
-		goto err4;
+		goto phys_err;
 	}
 
 	if (node) {
 		ret = of_platform_populate(node, NULL, NULL, dev);
 		if (ret) {
 			dev_err(dev, "failed to add dwc3 core\n");
-			goto err5;
+			goto populate_err;
 		}
 	} else {
 		dev_err(dev, "no device node, failed to add dwc3 core\n");
 		ret = -ENODEV;
-		goto err5;
+		goto populate_err;
 	}
 
 	return 0;
 
-err5:
+populate_err:
 	platform_device_unregister(exynos->usb2_phy);
 	platform_device_unregister(exynos->usb3_phy);
-err4:
+phys_err:
 	regulator_disable(exynos->vdd10);
-err3:
+vdd10_err:
 	regulator_disable(exynos->vdd33);
-err2:
+vdd33_err:
 	clk_disable_unprepare(exynos->axius_clk);
 axius_clk_err:
 	clk_disable_unprepare(exynos->susp_clk);

+ 24 - 24
drivers/usb/dwc3/dwc3-omap.c

@@ -79,40 +79,40 @@
 #define USBOTGSS_DEBUG_OFFSET			0x0600
 
 /* SYSCONFIG REGISTER */
-#define USBOTGSS_SYSCONFIG_DMADISABLE		(1 << 16)
+#define USBOTGSS_SYSCONFIG_DMADISABLE		BIT(16)
 
 /* IRQ_EOI REGISTER */
-#define USBOTGSS_IRQ_EOI_LINE_NUMBER		(1 << 0)
+#define USBOTGSS_IRQ_EOI_LINE_NUMBER		BIT(0)
 
 /* IRQS0 BITS */
-#define USBOTGSS_IRQO_COREIRQ_ST		(1 << 0)
+#define USBOTGSS_IRQO_COREIRQ_ST		BIT(0)
 
 /* IRQMISC BITS */
-#define USBOTGSS_IRQMISC_DMADISABLECLR		(1 << 17)
-#define USBOTGSS_IRQMISC_OEVT			(1 << 16)
-#define USBOTGSS_IRQMISC_DRVVBUS_RISE		(1 << 13)
-#define USBOTGSS_IRQMISC_CHRGVBUS_RISE		(1 << 12)
-#define USBOTGSS_IRQMISC_DISCHRGVBUS_RISE	(1 << 11)
-#define USBOTGSS_IRQMISC_IDPULLUP_RISE		(1 << 8)
-#define USBOTGSS_IRQMISC_DRVVBUS_FALL		(1 << 5)
-#define USBOTGSS_IRQMISC_CHRGVBUS_FALL		(1 << 4)
-#define USBOTGSS_IRQMISC_DISCHRGVBUS_FALL		(1 << 3)
-#define USBOTGSS_IRQMISC_IDPULLUP_FALL		(1 << 0)
+#define USBOTGSS_IRQMISC_DMADISABLECLR		BIT(17)
+#define USBOTGSS_IRQMISC_OEVT			BIT(16)
+#define USBOTGSS_IRQMISC_DRVVBUS_RISE		BIT(13)
+#define USBOTGSS_IRQMISC_CHRGVBUS_RISE		BIT(12)
+#define USBOTGSS_IRQMISC_DISCHRGVBUS_RISE	BIT(11)
+#define USBOTGSS_IRQMISC_IDPULLUP_RISE		BIT(8)
+#define USBOTGSS_IRQMISC_DRVVBUS_FALL		BIT(5)
+#define USBOTGSS_IRQMISC_CHRGVBUS_FALL		BIT(4)
+#define USBOTGSS_IRQMISC_DISCHRGVBUS_FALL		BIT(3)
+#define USBOTGSS_IRQMISC_IDPULLUP_FALL		BIT(0)
 
 /* UTMI_OTG_STATUS REGISTER */
-#define USBOTGSS_UTMI_OTG_STATUS_DRVVBUS	(1 << 5)
-#define USBOTGSS_UTMI_OTG_STATUS_CHRGVBUS	(1 << 4)
-#define USBOTGSS_UTMI_OTG_STATUS_DISCHRGVBUS	(1 << 3)
-#define USBOTGSS_UTMI_OTG_STATUS_IDPULLUP	(1 << 0)
+#define USBOTGSS_UTMI_OTG_STATUS_DRVVBUS	BIT(5)
+#define USBOTGSS_UTMI_OTG_STATUS_CHRGVBUS	BIT(4)
+#define USBOTGSS_UTMI_OTG_STATUS_DISCHRGVBUS	BIT(3)
+#define USBOTGSS_UTMI_OTG_STATUS_IDPULLUP	BIT(0)
 
 /* UTMI_OTG_CTRL REGISTER */
-#define USBOTGSS_UTMI_OTG_CTRL_SW_MODE		(1 << 31)
-#define USBOTGSS_UTMI_OTG_CTRL_POWERPRESENT	(1 << 9)
-#define USBOTGSS_UTMI_OTG_CTRL_TXBITSTUFFENABLE (1 << 8)
-#define USBOTGSS_UTMI_OTG_CTRL_IDDIG		(1 << 4)
-#define USBOTGSS_UTMI_OTG_CTRL_SESSEND		(1 << 3)
-#define USBOTGSS_UTMI_OTG_CTRL_SESSVALID	(1 << 2)
-#define USBOTGSS_UTMI_OTG_CTRL_VBUSVALID	(1 << 1)
+#define USBOTGSS_UTMI_OTG_CTRL_SW_MODE		BIT(31)
+#define USBOTGSS_UTMI_OTG_CTRL_POWERPRESENT	BIT(9)
+#define USBOTGSS_UTMI_OTG_CTRL_TXBITSTUFFENABLE BIT(8)
+#define USBOTGSS_UTMI_OTG_CTRL_IDDIG		BIT(4)
+#define USBOTGSS_UTMI_OTG_CTRL_SESSEND		BIT(3)
+#define USBOTGSS_UTMI_OTG_CTRL_SESSVALID	BIT(2)
+#define USBOTGSS_UTMI_OTG_CTRL_VBUSVALID	BIT(1)
 
 struct dwc3_omap {
 	struct device		*dev;

+ 66 - 85
drivers/usb/dwc3/ep0.c

@@ -39,14 +39,13 @@ static void __dwc3_ep0_do_control_status(struct dwc3 *dwc, struct dwc3_ep *dep);
 static void __dwc3_ep0_do_control_data(struct dwc3 *dwc,
 		struct dwc3_ep *dep, struct dwc3_request *req);
 
-static void dwc3_ep0_prepare_one_trb(struct dwc3 *dwc, u8 epnum,
+static void dwc3_ep0_prepare_one_trb(struct dwc3_ep *dep,
 		dma_addr_t buf_dma, u32 len, u32 type, bool chain)
 {
 	struct dwc3_trb			*trb;
-	struct dwc3_ep			*dep;
-
-	dep = dwc->eps[epnum];
+	struct dwc3			*dwc;
 
+	dwc = dep->dwc;
 	trb = &dwc->ep0_trb[dep->trb_enqueue];
 
 	if (chain)
@@ -69,16 +68,17 @@ static void dwc3_ep0_prepare_one_trb(struct dwc3 *dwc, u8 epnum,
 	trace_dwc3_prepare_trb(dep, trb);
 }
 
-static int dwc3_ep0_start_trans(struct dwc3 *dwc, u8 epnum)
+static int dwc3_ep0_start_trans(struct dwc3_ep *dep)
 {
 	struct dwc3_gadget_ep_cmd_params params;
-	struct dwc3_ep			*dep;
+	struct dwc3			*dwc;
 	int				ret;
 
-	dep = dwc->eps[epnum];
 	if (dep->flags & DWC3_EP_BUSY)
 		return 0;
 
+	dwc = dep->dwc;
+
 	memset(&params, 0, sizeof(params));
 	params.param0 = upper_32_bits(dwc->ep0_trb_addr);
 	params.param1 = lower_32_bits(dwc->ep0_trb_addr);
@@ -279,13 +279,15 @@ int dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value)
 
 void dwc3_ep0_out_start(struct dwc3 *dwc)
 {
+	struct dwc3_ep			*dep;
 	int				ret;
 
 	complete(&dwc->ep0_in_setup);
 
-	dwc3_ep0_prepare_one_trb(dwc, 0, dwc->ctrl_req_addr, 8,
+	dep = dwc->eps[0];
+	dwc3_ep0_prepare_one_trb(dep, dwc->ep0_trb_addr, 8,
 			DWC3_TRBCTL_CONTROL_SETUP, false);
-	ret = dwc3_ep0_start_trans(dwc, 0);
+	ret = dwc3_ep0_start_trans(dep);
 	WARN_ON(ret < 0);
 }
 
@@ -794,7 +796,7 @@ static int dwc3_ep0_std_request(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl)
 static void dwc3_ep0_inspect_setup(struct dwc3 *dwc,
 		const struct dwc3_event_depevt *event)
 {
-	struct usb_ctrlrequest *ctrl = dwc->ctrl_req;
+	struct usb_ctrlrequest *ctrl = (void *) dwc->ep0_trb;
 	int ret = -EINVAL;
 	u32 len;
 
@@ -834,7 +836,6 @@ static void dwc3_ep0_complete_data(struct dwc3 *dwc,
 	struct usb_request	*ur;
 	struct dwc3_trb		*trb;
 	struct dwc3_ep		*ep0;
-	unsigned		transfer_size = 0;
 	unsigned		maxp;
 	unsigned		remaining_ur_length;
 	void			*buf;
@@ -847,9 +848,7 @@ static void dwc3_ep0_complete_data(struct dwc3 *dwc,
 	ep0 = dwc->eps[0];
 
 	dwc->ep0_next_event = DWC3_EP0_NRDY_STATUS;
-
 	trb = dwc->ep0_trb;
-
 	trace_dwc3_complete_trb(ep0, trb);
 
 	r = next_request(&ep0->pending_list);
@@ -870,58 +869,23 @@ static void dwc3_ep0_complete_data(struct dwc3 *dwc,
 	remaining_ur_length = ur->length;
 
 	length = trb->size & DWC3_TRB_SIZE_MASK;
-
 	maxp = ep0->endpoint.maxpacket;
-
-	if (dwc->ep0_bounced) {
-		/*
-		 * Handle the first TRB before handling the bounce buffer if
-		 * the request length is greater than the bounce buffer size
-		 */
-		if (ur->length > DWC3_EP0_BOUNCE_SIZE) {
-			transfer_size = ALIGN(ur->length - maxp, maxp);
-			transferred = transfer_size - length;
-			buf = (u8 *)buf + transferred;
-			ur->actual += transferred;
-			remaining_ur_length -= transferred;
-
-			trb++;
-			length = trb->size & DWC3_TRB_SIZE_MASK;
-
-			ep0->trb_enqueue = 0;
-		}
-
-		transfer_size = roundup((ur->length - transfer_size),
-					maxp);
-
-		transferred = min_t(u32, remaining_ur_length,
-				    transfer_size - length);
-		memcpy(buf, dwc->ep0_bounce, transferred);
-	} else {
-		transferred = ur->length - length;
-	}
-
+	transferred = ur->length - length;
 	ur->actual += transferred;
 
-	if ((epnum & 1) && ur->actual < ur->length) {
-		/* for some reason we did not get everything out */
+	if ((IS_ALIGNED(ur->length, ep0->endpoint.maxpacket) &&
+	     ur->length && ur->zero) || dwc->ep0_bounced) {
+		trb++;
+		trb->ctrl &= ~DWC3_TRB_CTRL_HWO;
+		trace_dwc3_complete_trb(ep0, trb);
+		ep0->trb_enqueue = 0;
+		dwc->ep0_bounced = false;
+	}
 
+	if ((epnum & 1) && ur->actual < ur->length)
 		dwc3_ep0_stall_and_restart(dwc);
-	} else {
+	else
 		dwc3_gadget_giveback(ep0, r, 0);
-
-		if (IS_ALIGNED(ur->length, ep0->endpoint.maxpacket) &&
-				ur->length && ur->zero) {
-			int ret;
-
-			dwc->ep0_next_event = DWC3_EP0_COMPLETE;
-
-			dwc3_ep0_prepare_one_trb(dwc, epnum, dwc->ctrl_req_addr,
-					0, DWC3_TRBCTL_CONTROL_DATA, false);
-			ret = dwc3_ep0_start_trans(dwc, epnum);
-			WARN_ON(ret < 0);
-		}
-	}
 }
 
 static void dwc3_ep0_complete_status(struct dwc3 *dwc,
@@ -997,14 +961,13 @@ static void __dwc3_ep0_do_control_data(struct dwc3 *dwc,
 	req->direction = !!dep->number;
 
 	if (req->request.length == 0) {
-		dwc3_ep0_prepare_one_trb(dwc, dep->number,
-				dwc->ctrl_req_addr, 0,
+		dwc3_ep0_prepare_one_trb(dep, dwc->ep0_trb_addr, 0,
 				DWC3_TRBCTL_CONTROL_DATA, false);
-		ret = dwc3_ep0_start_trans(dwc, dep->number);
+		ret = dwc3_ep0_start_trans(dep);
 	} else if (!IS_ALIGNED(req->request.length, dep->endpoint.maxpacket)
 			&& (dep->number == 0)) {
-		u32	transfer_size = 0;
 		u32	maxpacket;
+		u32	rem;
 
 		ret = usb_gadget_map_request_by_dev(dwc->sysdev,
 				&req->request, dep->number);
@@ -1012,36 +975,55 @@ static void __dwc3_ep0_do_control_data(struct dwc3 *dwc,
 			return;
 
 		maxpacket = dep->endpoint.maxpacket;
+		rem = req->request.length % maxpacket;
+		dwc->ep0_bounced = true;
 
-		if (req->request.length > DWC3_EP0_BOUNCE_SIZE) {
-			transfer_size = ALIGN(req->request.length - maxpacket,
-					      maxpacket);
-			dwc3_ep0_prepare_one_trb(dwc, dep->number,
-						   req->request.dma,
-						   transfer_size,
-						   DWC3_TRBCTL_CONTROL_DATA,
-						   true);
-		}
-
-		transfer_size = roundup((req->request.length - transfer_size),
-					maxpacket);
+		/* prepare normal TRB */
+		dwc3_ep0_prepare_one_trb(dep, req->request.dma,
+					 req->request.length,
+					 DWC3_TRBCTL_CONTROL_DATA,
+					 true);
+
+		/* Now prepare one extra TRB to align transfer size */
+		dwc3_ep0_prepare_one_trb(dep, dwc->bounce_addr,
+					 maxpacket - rem,
+					 DWC3_TRBCTL_CONTROL_DATA,
+					 false);
+		ret = dwc3_ep0_start_trans(dep);
+	} else if (IS_ALIGNED(req->request.length, dep->endpoint.maxpacket) &&
+		   req->request.length && req->request.zero) {
+		u32	maxpacket;
+		u32	rem;
 
-		dwc->ep0_bounced = true;
+		ret = usb_gadget_map_request_by_dev(dwc->sysdev,
+				&req->request, dep->number);
+		if (ret)
+			return;
 
-		dwc3_ep0_prepare_one_trb(dwc, dep->number,
-				dwc->ep0_bounce_addr, transfer_size,
-				DWC3_TRBCTL_CONTROL_DATA, false);
-		ret = dwc3_ep0_start_trans(dwc, dep->number);
+		maxpacket = dep->endpoint.maxpacket;
+		rem = req->request.length % maxpacket;
+
+		/* prepare normal TRB */
+		dwc3_ep0_prepare_one_trb(dep, req->request.dma,
+					 req->request.length,
+					 DWC3_TRBCTL_CONTROL_DATA,
+					 true);
+
+		/* Now prepare one extra TRB to align transfer size */
+		dwc3_ep0_prepare_one_trb(dep, dwc->bounce_addr,
+					 0, DWC3_TRBCTL_CONTROL_DATA,
+					 false);
+		ret = dwc3_ep0_start_trans(dep);
 	} else {
 		ret = usb_gadget_map_request_by_dev(dwc->sysdev,
 				&req->request, dep->number);
 		if (ret)
 			return;
 
-		dwc3_ep0_prepare_one_trb(dwc, dep->number, req->request.dma,
+		dwc3_ep0_prepare_one_trb(dep, req->request.dma,
 				req->request.length, DWC3_TRBCTL_CONTROL_DATA,
 				false);
-		ret = dwc3_ep0_start_trans(dwc, dep->number);
+		ret = dwc3_ep0_start_trans(dep);
 	}
 
 	WARN_ON(ret < 0);
@@ -1055,9 +1037,8 @@ static int dwc3_ep0_start_control_status(struct dwc3_ep *dep)
 	type = dwc->three_stage_setup ? DWC3_TRBCTL_CONTROL_STATUS3
 		: DWC3_TRBCTL_CONTROL_STATUS2;
 
-	dwc3_ep0_prepare_one_trb(dwc, dep->number,
-			dwc->ctrl_req_addr, 0, type, false);
-	return dwc3_ep0_start_trans(dwc, dep->number);
+	dwc3_ep0_prepare_one_trb(dep, dwc->ep0_trb_addr, 0, type, false);
+	return dwc3_ep0_start_trans(dep);
 }
 
 static void __dwc3_ep0_do_control_status(struct dwc3 *dwc, struct dwc3_ep *dep)

+ 52 - 145
drivers/usb/dwc3/gadget.c

@@ -171,7 +171,6 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req,
 		int status)
 {
 	struct dwc3			*dwc = dep->dwc;
-	unsigned int			unmap_after_complete = false;
 
 	req->started = false;
 	list_del(&req->list);
@@ -181,19 +180,8 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req,
 	if (req->request.status == -EINPROGRESS)
 		req->request.status = status;
 
-	/*
-	 * NOTICE we don't want to unmap before calling ->complete() if we're
-	 * dealing with a bounced ep0 request. If we unmap it here, we would end
-	 * up overwritting the contents of req->buf and this could confuse the
-	 * gadget driver.
-	 */
-	if (dwc->ep0_bounced && dep->number <= 1) {
-		dwc->ep0_bounced = false;
-		unmap_after_complete = true;
-	} else {
-		usb_gadget_unmap_request_by_dev(dwc->sysdev,
-				&req->request, req->direction);
-	}
+	usb_gadget_unmap_request_by_dev(dwc->sysdev,
+					&req->request, req->direction);
 
 	trace_dwc3_gadget_giveback(req);
 
@@ -201,10 +189,6 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req,
 	usb_gadget_giveback_request(&dep->endpoint, &req->request);
 	spin_lock(&dwc->lock);
 
-	if (unmap_after_complete)
-		usb_gadget_unmap_request_by_dev(dwc->sysdev,
-				&req->request, req->direction);
-
 	if (dep->number > 1)
 		pm_runtime_put(dwc->dev);
 }
@@ -1060,6 +1044,22 @@ static void dwc3_prepare_one_trb_linear(struct dwc3_ep *dep,
 				false, 0, req->request.stream_id,
 				req->request.short_not_ok,
 				req->request.no_interrupt);
+	} else if (req->request.zero && req->request.length &&
+		   (IS_ALIGNED(req->request.length,dep->endpoint.maxpacket))) {
+		struct dwc3	*dwc = dep->dwc;
+		struct dwc3_trb	*trb;
+
+		req->zero = true;
+
+		/* prepare normal TRB */
+		dwc3_prepare_one_trb(dep, req, true, 0);
+
+		/* Now prepare one extra TRB to handle ZLP */
+		trb = &dep->trb_pool[dep->trb_enqueue];
+		__dwc3_prepare_one_trb(dep, trb, dwc->bounce_addr, 0,
+				false, 0, req->request.stream_id,
+				req->request.short_not_ok,
+				req->request.no_interrupt);
 	} else {
 		dwc3_prepare_one_trb(dep, req, false, 0);
 	}
@@ -1184,8 +1184,11 @@ static void __dwc3_gadget_start_isoc(struct dwc3 *dwc,
 		return;
 	}
 
-	/* 4 micro frames in the future */
-	uf = cur_uf + dep->interval * 4;
+	/*
+	 * Schedule the first trb for one interval in the future or at
+	 * least 4 microframes.
+	 */
+	uf = cur_uf + max_t(u32, 4, dep->interval);
 
 	__dwc3_gadget_kick_transfer(dep, uf);
 }
@@ -1272,31 +1275,6 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req)
 	return ret;
 }
 
-static void __dwc3_gadget_ep_zlp_complete(struct usb_ep *ep,
-		struct usb_request *request)
-{
-	dwc3_gadget_ep_free_request(ep, request);
-}
-
-static int __dwc3_gadget_ep_queue_zlp(struct dwc3 *dwc, struct dwc3_ep *dep)
-{
-	struct dwc3_request		*req;
-	struct usb_request		*request;
-	struct usb_ep			*ep = &dep->endpoint;
-
-	request = dwc3_gadget_ep_alloc_request(ep, GFP_ATOMIC);
-	if (!request)
-		return -ENOMEM;
-
-	request->length = 0;
-	request->buf = dwc->zlp_buf;
-	request->complete = __dwc3_gadget_ep_zlp_complete;
-
-	req = to_dwc3_request(request);
-
-	return __dwc3_gadget_ep_queue(dep, req);
-}
-
 static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request,
 	gfp_t gfp_flags)
 {
@@ -1310,17 +1288,6 @@ static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request,
 
 	spin_lock_irqsave(&dwc->lock, flags);
 	ret = __dwc3_gadget_ep_queue(dep, req);
-
-	/*
-	 * Okay, here's the thing, if gadget driver has requested for a ZLP by
-	 * setting request->zero, instead of doing magic, we will just queue an
-	 * extra usb_request ourselves so that it gets handled the same way as
-	 * any other request.
-	 */
-	if (ret == 0 && request->zero && request->length &&
-	    (request->length % ep->maxpacket == 0))
-		ret = __dwc3_gadget_ep_queue_zlp(dwc, dep);
-
 	spin_unlock_irqrestore(&dwc->lock, flags);
 
 	return ret;
@@ -1400,7 +1367,7 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep,
 					dwc3_ep_inc_deq(dep);
 				}
 
-				if (r->unaligned) {
+				if (r->unaligned || r->zero) {
 					trb = r->trb + r->num_pending_sgs + 1;
 					trb->ctrl &= ~DWC3_TRB_CTRL_HWO;
 					dwc3_ep_inc_deq(dep);
@@ -1411,7 +1378,7 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep,
 				trb->ctrl &= ~DWC3_TRB_CTRL_HWO;
 				dwc3_ep_inc_deq(dep);
 
-				if (r->unaligned) {
+				if (r->unaligned || r->zero) {
 					trb = r->trb + 1;
 					trb->ctrl &= ~DWC3_TRB_CTRL_HWO;
 					dwc3_ep_inc_deq(dep);
@@ -2006,14 +1973,15 @@ static const struct usb_gadget_ops dwc3_gadget_ops = {
 
 /* -------------------------------------------------------------------------- */
 
-static int dwc3_gadget_init_hw_endpoints(struct dwc3 *dwc,
-		u8 num, u32 direction)
+static int dwc3_gadget_init_endpoints(struct dwc3 *dwc, u8 num)
 {
 	struct dwc3_ep			*dep;
-	u8				i;
+	u8				epnum;
+
+	INIT_LIST_HEAD(&dwc->gadget.ep_list);
 
-	for (i = 0; i < num; i++) {
-		u8 epnum = (i << 1) | (direction ? 1 : 0);
+	for (epnum = 0; epnum < num; epnum++) {
+		bool			direction = epnum & 1;
 
 		dep = kzalloc(sizeof(*dep), GFP_KERNEL);
 		if (!dep)
@@ -2021,12 +1989,12 @@ static int dwc3_gadget_init_hw_endpoints(struct dwc3 *dwc,
 
 		dep->dwc = dwc;
 		dep->number = epnum;
-		dep->direction = !!direction;
+		dep->direction = direction;
 		dep->regs = dwc->regs + DWC3_DEP_BASE(epnum);
 		dwc->eps[epnum] = dep;
 
 		snprintf(dep->name, sizeof(dep->name), "ep%d%s", epnum >> 1,
-				(epnum & 1) ? "in" : "out");
+				direction ? "in" : "out");
 
 		dep->endpoint.name = dep->name;
 
@@ -2053,7 +2021,7 @@ static int dwc3_gadget_init_hw_endpoints(struct dwc3 *dwc,
 			/* MDWIDTH is represented in bits, we need it in bytes */
 			mdwidth /= 8;
 
-			size = dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(i));
+			size = dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(epnum >> 1));
 			size = DWC3_GTXFIFOSIZ_TXFDEF(size);
 
 			/* FIFO Depth is in MDWDITH bytes. Multiply */
@@ -2103,7 +2071,7 @@ static int dwc3_gadget_init_hw_endpoints(struct dwc3 *dwc,
 			dep->endpoint.caps.type_int = true;
 		}
 
-		dep->endpoint.caps.dir_in = !!direction;
+		dep->endpoint.caps.dir_in = direction;
 		dep->endpoint.caps.dir_out = !direction;
 
 		INIT_LIST_HEAD(&dep->pending_list);
@@ -2113,27 +2081,6 @@ static int dwc3_gadget_init_hw_endpoints(struct dwc3 *dwc,
 	return 0;
 }
 
-static int dwc3_gadget_init_endpoints(struct dwc3 *dwc)
-{
-	int				ret;
-
-	INIT_LIST_HEAD(&dwc->gadget.ep_list);
-
-	ret = dwc3_gadget_init_hw_endpoints(dwc, dwc->num_out_eps, 0);
-	if (ret < 0) {
-		dev_err(dwc->dev, "failed to initialize OUT endpoints\n");
-		return ret;
-	}
-
-	ret = dwc3_gadget_init_hw_endpoints(dwc, dwc->num_in_eps, 1);
-	if (ret < 0) {
-		dev_err(dwc->dev, "failed to initialize IN endpoints\n");
-		return ret;
-	}
-
-	return 0;
-}
-
 static void dwc3_gadget_free_endpoints(struct dwc3 *dwc)
 {
 	struct dwc3_ep			*dep;
@@ -2197,7 +2144,7 @@ static int __dwc3_cleanup_done_trbs(struct dwc3 *dwc, struct dwc3_ep *dep,
 	 * with one TRB pending in the ring. We need to manually clear HWO bit
 	 * from that TRB.
 	 */
-	if (req->unaligned && (trb->ctrl & DWC3_TRB_CTRL_HWO)) {
+	if ((req->zero || req->unaligned) && (trb->ctrl & DWC3_TRB_CTRL_HWO)) {
 		trb->ctrl &= ~DWC3_TRB_CTRL_HWO;
 		return 1;
 	}
@@ -2291,11 +2238,12 @@ static int dwc3_cleanup_done_reqs(struct dwc3 *dwc, struct dwc3_ep *dep,
 					event, status, chain);
 		}
 
-		if (req->unaligned) {
+		if (req->unaligned || req->zero) {
 			trb = &dep->trb_pool[dep->trb_dequeue];
 			ret = __dwc3_cleanup_done_trbs(dwc, dep, req, trb,
 					event, status, false);
 			req->unaligned = false;
+			req->zero = false;
 		}
 
 		req->request.actual = length - req->remaining;
@@ -3161,49 +3109,26 @@ int dwc3_gadget_init(struct dwc3 *dwc)
 
 	dwc->irq_gadget = irq;
 
-	dwc->ctrl_req = dma_alloc_coherent(dwc->sysdev, sizeof(*dwc->ctrl_req),
-			&dwc->ctrl_req_addr, GFP_KERNEL);
-	if (!dwc->ctrl_req) {
-		dev_err(dwc->dev, "failed to allocate ctrl request\n");
-		ret = -ENOMEM;
-		goto err0;
-	}
-
 	dwc->ep0_trb = dma_alloc_coherent(dwc->sysdev,
 					  sizeof(*dwc->ep0_trb) * 2,
 					  &dwc->ep0_trb_addr, GFP_KERNEL);
 	if (!dwc->ep0_trb) {
 		dev_err(dwc->dev, "failed to allocate ep0 trb\n");
 		ret = -ENOMEM;
-		goto err1;
+		goto err0;
 	}
 
-	dwc->setup_buf = kzalloc(DWC3_EP0_BOUNCE_SIZE, GFP_KERNEL);
+	dwc->setup_buf = kzalloc(DWC3_EP0_SETUP_SIZE, GFP_KERNEL);
 	if (!dwc->setup_buf) {
 		ret = -ENOMEM;
-		goto err2;
-	}
-
-	dwc->ep0_bounce = dma_alloc_coherent(dwc->sysdev,
-			DWC3_EP0_BOUNCE_SIZE, &dwc->ep0_bounce_addr,
-			GFP_KERNEL);
-	if (!dwc->ep0_bounce) {
-		dev_err(dwc->dev, "failed to allocate ep0 bounce buffer\n");
-		ret = -ENOMEM;
-		goto err3;
-	}
-
-	dwc->zlp_buf = kzalloc(DWC3_ZLP_BUF_SIZE, GFP_KERNEL);
-	if (!dwc->zlp_buf) {
-		ret = -ENOMEM;
-		goto err4;
+		goto err1;
 	}
 
 	dwc->bounce = dma_alloc_coherent(dwc->sysdev, DWC3_BOUNCE_SIZE,
 			&dwc->bounce_addr, GFP_KERNEL);
 	if (!dwc->bounce) {
 		ret = -ENOMEM;
-		goto err5;
+		goto err2;
 	}
 
 	init_completion(&dwc->ep0_in_setup);
@@ -3241,39 +3166,31 @@ int dwc3_gadget_init(struct dwc3 *dwc)
 	 * sure we're starting from a well known location.
 	 */
 
-	ret = dwc3_gadget_init_endpoints(dwc);
+	ret = dwc3_gadget_init_endpoints(dwc, dwc->num_eps);
 	if (ret)
-		goto err6;
+		goto err3;
 
 	ret = usb_add_gadget_udc(dwc->dev, &dwc->gadget);
 	if (ret) {
 		dev_err(dwc->dev, "failed to register udc\n");
-		goto err6;
+		goto err4;
 	}
 
 	return 0;
-err6:
-	dma_free_coherent(dwc->sysdev, DWC3_BOUNCE_SIZE, dwc->bounce,
-			dwc->bounce_addr);
-
-err5:
-	kfree(dwc->zlp_buf);
 
 err4:
 	dwc3_gadget_free_endpoints(dwc);
-	dma_free_coherent(dwc->sysdev, DWC3_EP0_BOUNCE_SIZE,
-			dwc->ep0_bounce, dwc->ep0_bounce_addr);
 
 err3:
-	kfree(dwc->setup_buf);
+	dma_free_coherent(dwc->sysdev, DWC3_BOUNCE_SIZE, dwc->bounce,
+			dwc->bounce_addr);
 
 err2:
-	dma_free_coherent(dwc->sysdev, sizeof(*dwc->ep0_trb) * 2,
-			dwc->ep0_trb, dwc->ep0_trb_addr);
+	kfree(dwc->setup_buf);
 
 err1:
-	dma_free_coherent(dwc->sysdev, sizeof(*dwc->ctrl_req),
-			dwc->ctrl_req, dwc->ctrl_req_addr);
+	dma_free_coherent(dwc->sysdev, sizeof(*dwc->ep0_trb) * 2,
+			dwc->ep0_trb, dwc->ep0_trb_addr);
 
 err0:
 	return ret;
@@ -3284,22 +3201,12 @@ err0:
 void dwc3_gadget_exit(struct dwc3 *dwc)
 {
 	usb_del_gadget_udc(&dwc->gadget);
-
 	dwc3_gadget_free_endpoints(dwc);
-
 	dma_free_coherent(dwc->sysdev, DWC3_BOUNCE_SIZE, dwc->bounce,
-			dwc->bounce_addr);
-	dma_free_coherent(dwc->sysdev, DWC3_EP0_BOUNCE_SIZE,
-			dwc->ep0_bounce, dwc->ep0_bounce_addr);
-
+			  dwc->bounce_addr);
 	kfree(dwc->setup_buf);
-	kfree(dwc->zlp_buf);
-
 	dma_free_coherent(dwc->sysdev, sizeof(*dwc->ep0_trb) * 2,
-			dwc->ep0_trb, dwc->ep0_trb_addr);
-
-	dma_free_coherent(dwc->sysdev, sizeof(*dwc->ctrl_req),
-			dwc->ctrl_req, dwc->ctrl_req_addr);
+			  dwc->ep0_trb, dwc->ep0_trb_addr);
 }
 
 int dwc3_gadget_suspend(struct dwc3 *dwc)

+ 10 - 10
drivers/usb/dwc3/gadget.h

@@ -29,16 +29,16 @@ struct dwc3;
 
 /* DEPCFG parameter 1 */
 #define DWC3_DEPCFG_INT_NUM(n)		(((n) & 0x1f) << 0)
-#define DWC3_DEPCFG_XFER_COMPLETE_EN	(1 << 8)
-#define DWC3_DEPCFG_XFER_IN_PROGRESS_EN	(1 << 9)
-#define DWC3_DEPCFG_XFER_NOT_READY_EN	(1 << 10)
-#define DWC3_DEPCFG_FIFO_ERROR_EN	(1 << 11)
-#define DWC3_DEPCFG_STREAM_EVENT_EN	(1 << 13)
+#define DWC3_DEPCFG_XFER_COMPLETE_EN	BIT(8)
+#define DWC3_DEPCFG_XFER_IN_PROGRESS_EN	BIT(9)
+#define DWC3_DEPCFG_XFER_NOT_READY_EN	BIT(10)
+#define DWC3_DEPCFG_FIFO_ERROR_EN	BIT(11)
+#define DWC3_DEPCFG_STREAM_EVENT_EN	BIT(12)
 #define DWC3_DEPCFG_BINTERVAL_M1(n)	(((n) & 0xff) << 16)
-#define DWC3_DEPCFG_STREAM_CAPABLE	(1 << 24)
+#define DWC3_DEPCFG_STREAM_CAPABLE	BIT(24)
 #define DWC3_DEPCFG_EP_NUMBER(n)	(((n) & 0x1f) << 25)
-#define DWC3_DEPCFG_BULK_BASED		(1 << 30)
-#define DWC3_DEPCFG_FIFO_BASED		(1 << 31)
+#define DWC3_DEPCFG_BULK_BASED		BIT(30)
+#define DWC3_DEPCFG_FIFO_BASED		BIT(31)
 
 /* DEPCFG parameter 0 */
 #define DWC3_DEPCFG_EP_TYPE(n)		(((n) & 0x3) << 1)
@@ -47,10 +47,10 @@ struct dwc3;
 #define DWC3_DEPCFG_BURST_SIZE(n)	(((n) & 0xf) << 22)
 #define DWC3_DEPCFG_DATA_SEQ_NUM(n)	((n) << 26)
 /* This applies for core versions earlier than 1.94a */
-#define DWC3_DEPCFG_IGN_SEQ_NUM		(1 << 31)
+#define DWC3_DEPCFG_IGN_SEQ_NUM		BIT(31)
 /* These apply for core versions 1.94a and later */
 #define DWC3_DEPCFG_ACTION_INIT		(0 << 30)
-#define DWC3_DEPCFG_ACTION_RESTORE	(1 << 30)
+#define DWC3_DEPCFG_ACTION_RESTORE	BIT(30)
 #define DWC3_DEPCFG_ACTION_MODIFY	(2 << 30)
 
 /* DEPXFERCFG parameter 0 */

+ 2 - 56
drivers/usb/dwc3/trace.h

@@ -27,31 +27,6 @@
 #include "core.h"
 #include "debug.h"
 
-DECLARE_EVENT_CLASS(dwc3_log_msg,
-	TP_PROTO(struct va_format *vaf),
-	TP_ARGS(vaf),
-	TP_STRUCT__entry(__dynamic_array(char, msg, DWC3_MSG_MAX)),
-	TP_fast_assign(
-		vsnprintf(__get_str(msg), DWC3_MSG_MAX, vaf->fmt, *vaf->va);
-	),
-	TP_printk("%s", __get_str(msg))
-);
-
-DEFINE_EVENT(dwc3_log_msg, dwc3_gadget,
-	TP_PROTO(struct va_format *vaf),
-	TP_ARGS(vaf)
-);
-
-DEFINE_EVENT(dwc3_log_msg, dwc3_core,
-	TP_PROTO(struct va_format *vaf),
-	TP_ARGS(vaf)
-);
-
-DEFINE_EVENT(dwc3_log_msg, dwc3_ep0,
-	TP_PROTO(struct va_format *vaf),
-	TP_ARGS(vaf)
-);
-
 DECLARE_EVENT_CLASS(dwc3_log_io,
 	TP_PROTO(void *base, u32 offset, u32 value),
 	TP_ARGS(base, offset, value),
@@ -198,7 +173,7 @@ DECLARE_EVENT_CLASS(dwc3_log_generic_cmd,
 		__entry->param = param;
 		__entry->status = status;
 	),
-	TP_printk("cmd '%s' [%d] param %08x --> status: %s",
+	TP_printk("cmd '%s' [%x] param %08x --> status: %s",
 		dwc3_gadget_generic_cmd_string(__entry->cmd),
 		__entry->cmd, __entry->param,
 		dwc3_gadget_generic_cmd_status_string(__entry->status)
@@ -298,36 +273,7 @@ DECLARE_EVENT_CLASS(dwc3_log_trb,
 		__entry->ctrl & DWC3_TRB_CTRL_CSP ? 'S' : 's',
 		__entry->ctrl & DWC3_TRB_CTRL_ISP_IMI ? 'S' : 's',
 		__entry->ctrl & DWC3_TRB_CTRL_IOC ? 'C' : 'c',
-		({char *s;
-		switch (__entry->ctrl & 0x3f0) {
-		case DWC3_TRBCTL_NORMAL:
-			s = "normal";
-			break;
-		case DWC3_TRBCTL_CONTROL_SETUP:
-			s = "setup";
-			break;
-		case DWC3_TRBCTL_CONTROL_STATUS2:
-			s = "status2";
-			break;
-		case DWC3_TRBCTL_CONTROL_STATUS3:
-			s = "status3";
-			break;
-		case DWC3_TRBCTL_CONTROL_DATA:
-			s = "data";
-			break;
-		case DWC3_TRBCTL_ISOCHRONOUS_FIRST:
-			s = "isoc-first";
-			break;
-		case DWC3_TRBCTL_ISOCHRONOUS:
-			s = "isoc";
-			break;
-		case DWC3_TRBCTL_LINK_TRB:
-			s = "link";
-			break;
-		default:
-			s = "UNKNOWN";
-			break;
-		} s; })
+		  dwc3_trb_type_string(DWC3_TRBCTL_TYPE(__entry->ctrl))
 	)
 );
 

+ 9 - 2
drivers/usb/gadget/Kconfig

@@ -212,7 +212,7 @@ config USB_F_TCM
 # this first set of drivers all depend on bulk-capable hardware.
 
 config USB_CONFIGFS
-	tristate "USB functions configurable through configfs"
+	tristate "USB Gadget functions configurable through configfs"
 	select USB_LIBCOMPOSITE
 	help
 	  A Linux USB "gadget" can be set up through configfs.
@@ -458,8 +458,9 @@ config USB_CONFIGFS_F_TCM
 	  UAS utilizes the USB 3.0 feature called streams support.
 
 choice
-	tristate "USB Gadget Drivers"
+	tristate "USB Gadget precomposed configurations"
 	default USB_ETH
+	optional
 	help
 	  A Linux "Gadget Driver" talks to the USB Peripheral Controller
 	  driver through the abstract "gadget" API.  Some other operating
@@ -476,6 +477,12 @@ choice
 	  not be able work with that controller, or might need to implement
 	  a less common variant of a device class protocol.
 
+	  The available choices each represent a single precomposed USB
+	  gadget configuration. In the device model, each option contains
+	  both the device instantiation as a child for a USB gadget
+	  controller, and the relevant drivers for each function declared
+	  by the device.
+
 source "drivers/usb/gadget/legacy/Kconfig"
 
 endchoice

+ 17 - 63
drivers/usb/gadget/function/f_fs.c

@@ -246,7 +246,6 @@ EXPORT_SYMBOL_GPL(ffs_lock);
 
 static struct ffs_dev *_ffs_find_dev(const char *name);
 static struct ffs_dev *_ffs_alloc_dev(void);
-static int _ffs_name_dev(struct ffs_dev *dev, const char *name);
 static void _ffs_free_dev(struct ffs_dev *dev);
 static void *ffs_acquire_dev(const char *dev_name);
 static void ffs_release_dev(struct ffs_data *ffs_data);
@@ -1571,14 +1570,14 @@ static void ffs_data_get(struct ffs_data *ffs)
 {
 	ENTER();
 
-	atomic_inc(&ffs->ref);
+	refcount_inc(&ffs->ref);
 }
 
 static void ffs_data_opened(struct ffs_data *ffs)
 {
 	ENTER();
 
-	atomic_inc(&ffs->ref);
+	refcount_inc(&ffs->ref);
 	if (atomic_add_return(1, &ffs->opened) == 1 &&
 			ffs->state == FFS_DEACTIVATED) {
 		ffs->state = FFS_CLOSING;
@@ -1590,7 +1589,7 @@ static void ffs_data_put(struct ffs_data *ffs)
 {
 	ENTER();
 
-	if (unlikely(atomic_dec_and_test(&ffs->ref))) {
+	if (unlikely(refcount_dec_and_test(&ffs->ref))) {
 		pr_info("%s(): freeing\n", __func__);
 		ffs_data_clear(ffs);
 		BUG_ON(waitqueue_active(&ffs->ev.waitq) ||
@@ -1635,7 +1634,7 @@ static struct ffs_data *ffs_data_new(void)
 
 	ENTER();
 
-	atomic_set(&ffs->ref, 1);
+	refcount_set(&ffs->ref, 1);
 	atomic_set(&ffs->opened, 0);
 	ffs->state = FFS_READ_DESCRIPTORS;
 	mutex_init(&ffs->mutex);
@@ -3302,9 +3301,10 @@ static struct ffs_dev *_ffs_do_find_dev(const char *name)
 {
 	struct ffs_dev *dev;
 
+	if (!name)
+		return NULL;
+
 	list_for_each_entry(dev, &ffs_devices, entry) {
-		if (!dev->name || !name)
-			continue;
 		if (strcmp(dev->name, name) == 0)
 			return dev;
 	}
@@ -3380,42 +3380,11 @@ static void ffs_free_inst(struct usb_function_instance *f)
 	kfree(opts);
 }
 
-#define MAX_INST_NAME_LEN	40
-
 static int ffs_set_inst_name(struct usb_function_instance *fi, const char *name)
 {
-	struct f_fs_opts *opts;
-	char *ptr;
-	const char *tmp;
-	int name_len, ret;
-
-	name_len = strlen(name) + 1;
-	if (name_len > MAX_INST_NAME_LEN)
+	if (strlen(name) >= FIELD_SIZEOF(struct ffs_dev, name))
 		return -ENAMETOOLONG;
-
-	ptr = kstrndup(name, name_len, GFP_KERNEL);
-	if (!ptr)
-		return -ENOMEM;
-
-	opts = to_f_fs_opts(fi);
-	tmp = NULL;
-
-	ffs_dev_lock();
-
-	tmp = opts->dev->name_allocated ? opts->dev->name : NULL;
-	ret = _ffs_name_dev(opts->dev, ptr);
-	if (ret) {
-		kfree(ptr);
-		ffs_dev_unlock();
-		return ret;
-	}
-	opts->dev->name_allocated = true;
-
-	ffs_dev_unlock();
-
-	kfree(tmp);
-
-	return 0;
+	return ffs_name_dev(to_f_fs_opts(fi)->dev, name);
 }
 
 static struct usb_function_instance *ffs_alloc_inst(void)
@@ -3545,32 +3514,19 @@ static struct ffs_dev *_ffs_alloc_dev(void)
 	return dev;
 }
 
-/*
- * ffs_lock must be taken by the caller of this function
- * The caller is responsible for "name" being available whenever f_fs needs it
- */
-static int _ffs_name_dev(struct ffs_dev *dev, const char *name)
+int ffs_name_dev(struct ffs_dev *dev, const char *name)
 {
 	struct ffs_dev *existing;
+	int ret = 0;
 
-	existing = _ffs_do_find_dev(name);
-	if (existing)
-		return -EBUSY;
-
-	dev->name = name;
-
-	return 0;
-}
+	ffs_dev_lock();
 
-/*
- * The caller is responsible for "name" being available whenever f_fs needs it
- */
-int ffs_name_dev(struct ffs_dev *dev, const char *name)
-{
-	int ret;
+	existing = _ffs_do_find_dev(name);
+	if (!existing)
+		strlcpy(dev->name, name, ARRAY_SIZE(dev->name));
+	else if (existing != dev)
+		ret = -EBUSY;
 
-	ffs_dev_lock();
-	ret = _ffs_name_dev(dev, name);
 	ffs_dev_unlock();
 
 	return ret;
@@ -3600,8 +3556,6 @@ EXPORT_SYMBOL_GPL(ffs_single_dev);
 static void _ffs_free_dev(struct ffs_dev *dev)
 {
 	list_del(&dev->entry);
-	if (dev->name_allocated)
-		kfree(dev->name);
 
 	/* Clear the private_data pointer to stop incorrect dev access */
 	if (dev->ffs_data)

+ 12 - 12
drivers/usb/gadget/function/u_ether.c

@@ -178,6 +178,7 @@ static void rx_complete(struct usb_ep *ep, struct usb_request *req);
 static int
 rx_submit(struct eth_dev *dev, struct usb_request *req, gfp_t gfp_flags)
 {
+	struct usb_gadget *g = dev->gadget;
 	struct sk_buff	*skb;
 	int		retval = -ENOMEM;
 	size_t		size = 0;
@@ -209,8 +210,11 @@ rx_submit(struct eth_dev *dev, struct usb_request *req, gfp_t gfp_flags)
 	 */
 	size += sizeof(struct ethhdr) + dev->net->mtu + RX_EXTRA;
 	size += dev->port_usb->header_len;
-	size += out->maxpacket - 1;
-	size -= size % out->maxpacket;
+
+	if (g->quirk_ep_out_aligned_size) {
+		size += out->maxpacket - 1;
+		size -= size % out->maxpacket;
+	}
 
 	if (dev->port_usb->is_fixed)
 		size = max_t(size_t, size, dev->port_usb->fixed_out_len);
@@ -401,13 +405,12 @@ done:
 static void rx_fill(struct eth_dev *dev, gfp_t gfp_flags)
 {
 	struct usb_request	*req;
+	struct usb_request	*tmp;
 	unsigned long		flags;
 
 	/* fill unused rxq slots with some skb */
 	spin_lock_irqsave(&dev->req_lock, flags);
-	while (!list_empty(&dev->rx_reqs)) {
-		req = container_of(dev->rx_reqs.next,
-				struct usb_request, list);
+	list_for_each_entry_safe(req, tmp, &dev->rx_reqs, list) {
 		list_del_init(&req->list);
 		spin_unlock_irqrestore(&dev->req_lock, flags);
 
@@ -527,7 +530,7 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb,
 		return NETDEV_TX_BUSY;
 	}
 
-	req = container_of(dev->tx_reqs.next, struct usb_request, list);
+	req = list_first_entry(&dev->tx_reqs, struct usb_request, list);
 	list_del(&req->list);
 
 	/* temporarily stop TX queue when the freelist empties */
@@ -1122,6 +1125,7 @@ void gether_disconnect(struct gether *link)
 {
 	struct eth_dev		*dev = link->ioport;
 	struct usb_request	*req;
+	struct usb_request	*tmp;
 
 	WARN_ON(!dev);
 	if (!dev)
@@ -1138,9 +1142,7 @@ void gether_disconnect(struct gether *link)
 	 */
 	usb_ep_disable(link->in_ep);
 	spin_lock(&dev->req_lock);
-	while (!list_empty(&dev->tx_reqs)) {
-		req = container_of(dev->tx_reqs.next,
-					struct usb_request, list);
+	list_for_each_entry_safe(req, tmp, &dev->tx_reqs, list) {
 		list_del(&req->list);
 
 		spin_unlock(&dev->req_lock);
@@ -1152,9 +1154,7 @@ void gether_disconnect(struct gether *link)
 
 	usb_ep_disable(link->out_ep);
 	spin_lock(&dev->req_lock);
-	while (!list_empty(&dev->rx_reqs)) {
-		req = container_of(dev->rx_reqs.next,
-					struct usb_request, list);
+	list_for_each_entry_safe(req, tmp, &dev->rx_reqs, list) {
 		list_del(&req->list);
 
 		spin_unlock(&dev->req_lock);

+ 8 - 6
drivers/usb/gadget/function/u_fs.h

@@ -20,6 +20,7 @@
 #include <linux/list.h>
 #include <linux/mutex.h>
 #include <linux/workqueue.h>
+#include <linux/refcount.h>
 
 #ifdef VERBOSE_DEBUG
 #ifndef pr_vdebug
@@ -39,15 +40,16 @@
 struct f_fs_opts;
 
 struct ffs_dev {
-	const char *name;
-	bool name_allocated;
-	bool mounted;
-	bool desc_ready;
-	bool single;
 	struct ffs_data *ffs_data;
 	struct f_fs_opts *opts;
 	struct list_head entry;
 
+	char name[41];
+
+	bool mounted;
+	bool desc_ready;
+	bool single;
+
 	int (*ffs_ready_callback)(struct ffs_data *ffs);
 	void (*ffs_closed_callback)(struct ffs_data *ffs);
 	void *(*ffs_acquire_dev_callback)(struct ffs_dev *dev);
@@ -177,7 +179,7 @@ struct ffs_data {
 	struct completion		ep0req_completion;	/* P: mutex */
 
 	/* reference counter */
-	atomic_t			ref;
+	refcount_t			ref;
 	/* how many files are opened (EP0 and others) */
 	atomic_t			opened;
 

+ 8 - 8
drivers/usb/gadget/function/uvc_configfs.c

@@ -2125,7 +2125,7 @@ static struct configfs_item_operations uvc_item_ops = {
 	.release		= uvc_attr_release,
 };
 
-#define UVCG_OPTS_ATTR(cname, conv, str2u, uxx, vnoc, limit)		\
+#define UVCG_OPTS_ATTR(cname, aname, conv, str2u, uxx, vnoc, limit)	\
 static ssize_t f_uvc_opts_##cname##_show(				\
 	struct config_item *item, char *page)				\
 {									\
@@ -2168,16 +2168,16 @@ end:									\
 	return ret;							\
 }									\
 									\
-UVC_ATTR(f_uvc_opts_, cname, aname)
+UVC_ATTR(f_uvc_opts_, cname, cname)
 
 #define identity_conv(x) (x)
 
-UVCG_OPTS_ATTR(streaming_interval, identity_conv, kstrtou8, u8, identity_conv,
-	       16);
-UVCG_OPTS_ATTR(streaming_maxpacket, le16_to_cpu, kstrtou16, u16, le16_to_cpu,
-	       3072);
-UVCG_OPTS_ATTR(streaming_maxburst, identity_conv, kstrtou8, u8, identity_conv,
-	       15);
+UVCG_OPTS_ATTR(streaming_interval, streaming_interval, identity_conv,
+	       kstrtou8, u8, identity_conv, 16);
+UVCG_OPTS_ATTR(streaming_maxpacket, streaming_maxpacket, le16_to_cpu,
+	       kstrtou16, u16, le16_to_cpu, 3072);
+UVCG_OPTS_ATTR(streaming_maxburst, streaming_maxburst, identity_conv,
+	       kstrtou8, u8, identity_conv, 15);
 
 #undef identity_conv
 

+ 9 - 8
drivers/usb/gadget/legacy/inode.c

@@ -27,6 +27,7 @@
 #include <linux/mmu_context.h>
 #include <linux/aio.h>
 #include <linux/uio.h>
+#include <linux/refcount.h>
 
 #include <linux/device.h>
 #include <linux/moduleparam.h>
@@ -114,7 +115,7 @@ enum ep0_state {
 
 struct dev_data {
 	spinlock_t			lock;
-	atomic_t			count;
+	refcount_t			count;
 	enum ep0_state			state;		/* P: lock */
 	struct usb_gadgetfs_event	event [N_EVENT];
 	unsigned			ev_next;
@@ -150,12 +151,12 @@ struct dev_data {
 
 static inline void get_dev (struct dev_data *data)
 {
-	atomic_inc (&data->count);
+	refcount_inc (&data->count);
 }
 
 static void put_dev (struct dev_data *data)
 {
-	if (likely (!atomic_dec_and_test (&data->count)))
+	if (likely (!refcount_dec_and_test (&data->count)))
 		return;
 	/* needs no more cleanup */
 	BUG_ON (waitqueue_active (&data->wait));
@@ -170,7 +171,7 @@ static struct dev_data *dev_new (void)
 	if (!dev)
 		return NULL;
 	dev->state = STATE_DEV_DISABLED;
-	atomic_set (&dev->count, 1);
+	refcount_set (&dev->count, 1);
 	spin_lock_init (&dev->lock);
 	INIT_LIST_HEAD (&dev->epfiles);
 	init_waitqueue_head (&dev->wait);
@@ -190,7 +191,7 @@ enum ep_state {
 struct ep_data {
 	struct mutex			lock;
 	enum ep_state			state;
-	atomic_t			count;
+	refcount_t			count;
 	struct dev_data			*dev;
 	/* must hold dev->lock before accessing ep or req */
 	struct usb_ep			*ep;
@@ -205,12 +206,12 @@ struct ep_data {
 
 static inline void get_ep (struct ep_data *data)
 {
-	atomic_inc (&data->count);
+	refcount_inc (&data->count);
 }
 
 static void put_ep (struct ep_data *data)
 {
-	if (likely (!atomic_dec_and_test (&data->count)))
+	if (likely (!refcount_dec_and_test (&data->count)))
 		return;
 	put_dev (data->dev);
 	/* needs no more cleanup */
@@ -1561,7 +1562,7 @@ static int activate_ep_files (struct dev_data *dev)
 		init_waitqueue_head (&data->wait);
 
 		strncpy (data->name, ep->name, sizeof (data->name) - 1);
-		atomic_set (&data->count, 1);
+		refcount_set (&data->count, 1);
 		data->dev = dev;
 		get_dev (dev);
 

+ 26 - 6
drivers/usb/gadget/udc/Kconfig

@@ -62,8 +62,9 @@ config USB_ATMEL_USBA
 
 	  The fifo_mode parameter is used to select endpoint allocation mode.
 	  fifo_mode = 0 is used to let the driver autoconfigure the endpoints.
-	  In this case 2 banks are allocated for isochronous endpoints and
-	  only one bank is allocated for the rest of the endpoints.
+	  In this case, for ep1 2 banks are allocated if it works in isochronous
+	  mode and only 1 bank otherwise. For the rest of the endpoints
+	  only 1 bank is allocated.
 
 	  fifo_mode = 1 is a generic maximum fifo size (1024 bytes) configuration
 	  allowing the usage of ep1 - ep6
@@ -191,6 +192,7 @@ config USB_RENESAS_USBHS_UDC
 config USB_RENESAS_USB3
 	tristate 'Renesas USB3.0 Peripheral controller'
 	depends on ARCH_RENESAS || COMPILE_TEST
+	depends on EXTCON
 	help
 	   Renesas USB3.0 Peripheral controller is a USB peripheral controller
 	   that supports super, high, and full speed USB 3.0 data transfers.
@@ -253,6 +255,20 @@ config USB_MV_U3D
 	  MARVELL PXA2128 Processor series include a super speed USB3.0 device
 	  controller, which support super speed USB peripheral.
 
+config USB_SNP_CORE
+	depends on USB_AMD5536UDC
+	tristate
+	help
+	  This enables core driver support for Synopsys USB 2.0 Device
+	  controller.
+
+	  This will be enabled when PCI or Platform driver for this UDC is
+	  selected. Currently, this will be enabled by USB_SNP_UDC_PLAT or
+	  USB_AMD5536UDC options.
+
+	  This IP is different to the High Speed OTG IP that can be enabled
+	  by selecting USB_DWC2 or USB_DWC3 options.
+
 #
 # Controllers available in both integrated and discrete versions
 #
@@ -277,7 +293,8 @@ source "drivers/usb/gadget/udc/bdc/Kconfig"
 
 config USB_AMD5536UDC
 	tristate "AMD5536 UDC"
-	depends on PCI
+	depends on USB_PCI
+	select USB_SNP_CORE
 	help
 	   The AMD5536 UDC is part of the AMD Geode CS5536, an x86 southbridge.
 	   It is a USB Highspeed DMA capable USB device controller. Beside ep0
@@ -285,6 +302,9 @@ config USB_AMD5536UDC
 	   The UDC port supports OTG operation, and may be used as a host port
 	   if it's not being used to implement peripheral or OTG roles.
 
+	   This UDC is based on Synopsys USB device controller IP and selects
+	   CONFIG_USB_SNP_CORE option to build the core driver.
+
 	   Say "y" to link the driver statically, or "m" to build a
 	   dynamically linked module called "amd5536udc" and force all
 	   gadget drivers to also be dynamically linked.
@@ -327,7 +347,7 @@ config USB_NET2272_DMA
 
 config USB_NET2280
 	tristate "NetChip NET228x / PLX USB3x8x"
-	depends on PCI
+	depends on USB_PCI
 	help
 	   NetChip 2280 / 2282 is a PCI based USB peripheral controller which
 	   supports both full and high speed USB 2.0 data transfers.
@@ -352,7 +372,7 @@ config USB_NET2280
 
 config USB_GOKU
 	tristate "Toshiba TC86C001 'Goku-S'"
-	depends on PCI
+	depends on USB_PCI
 	help
 	   The Toshiba TC86C001 is a PCI device which includes controllers
 	   for full speed USB devices, IDE, I2C, SIO, plus a USB host (OHCI).
@@ -366,7 +386,7 @@ config USB_GOKU
 
 config USB_EG20T
 	tristate "Intel QUARK X1000/EG20T PCH/LAPIS Semiconductor IOH(ML7213/ML7831) UDC"
-	depends on PCI
+	depends on USB_PCI
 	help
 	  This is a USB device driver for EG20T PCH.
 	  EG20T PCH is the platform controller hub that is used in Intel's

Some files were not shown because too many files changed in this diff