linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCHv13 0/3] USB Type-C Connector class
@ 2016-11-24 12:21 Heikki Krogerus
  2016-11-24 12:21 ` [PATCHv13 1/3] lib/string: add sysfs_match_string helper Heikki Krogerus
                   ` (2 more replies)
  0 siblings, 3 replies; 19+ messages in thread
From: Heikki Krogerus @ 2016-11-24 12:21 UTC (permalink / raw)
  To: Greg KH, Guenter Roeck; +Cc: Oliver Neukum, linux-kernel, linux-usb

The USB Type-C class is meant to provide unified interface to the
userspace to present the USB Type-C ports in a system.

Changes since v12:
- Added prefer_role member to typec_capability structure as requested by Guenter

Changes since v11:
- The port drivers are responsible of removing the alternate
  modes (just like the documentation already said).

Changes since v10:
- Using ATTRIBUTE_GROUPS and DEVICE_ATTR marcos everywhere
- Moved sysfs_match_string to lib/string.c
- Rationalized uevents
- Calling ida_destroy

Changes since v9:
- Minor typec_wcove.c cleanup as proposed by Guenter Roeck. No
  function affect.

Changes since v8:
- checking sysfs_streq() result correctly in sysfs_strmatch
- fixed accessory check in supported_accessory_mode
- using "none" as the only string that can clear the preferred role

Changes since v7:
- Removed "type" attribute from partners
- Added supports_usb_power_delivery attribute for partner and cable

Changes since v6:
- current_vconn_role attr renamed to vconn_source (no API changes)
- Small documentation improvements proposed by Vincent Palatin

Changes since v5:
- Only updating the roles based on driver notifications
- Added MODULE_ALIAS for the WhiskeyCove module
- Including the patch that creates the actual platform device for the
  WhiskeyCove Type-C PHY in this series.

Changes since v4:
- Remove the port lock completely

Changes since v3:
- Documentation cleanup as proposed by Roger Quadros
- Setting partner altmodes member to NULL on removal and fixing a
  warning, as proposed by Guenter Roeck
- Added the following attributes for partners and cables:
  * supports_usb_power_delivery
  * id_header_vdo
- "id_header_vdo" is visible only when the partner or cable supports
  USB Power Delivery communication.
- Partner attribute "accessory" is hidden when the partner type is not
  "Accessory".

Changes since v2:
- Notification on role and alternate mode changes
- cleanups

Changes since v1:
- Completely rewrote alternate mode support
- Patners, cables and cable plugs presented as devices.


Heikki Krogerus (3):
  lib/string: add sysfs_match_string helper
  usb: USB Type-C connector class
  usb: typec: add driver for Intel Whiskey Cove PMIC USB Type-C PHY

 Documentation/ABI/testing/sysfs-class-typec |  220 ++++++
 Documentation/usb/typec.txt                 |  110 +++
 MAINTAINERS                                 |    9 +
 drivers/usb/Kconfig                         |    2 +
 drivers/usb/Makefile                        |    2 +
 drivers/usb/typec/Kconfig                   |   21 +
 drivers/usb/typec/Makefile                  |    2 +
 drivers/usb/typec/typec.c                   | 1013 +++++++++++++++++++++++++++
 drivers/usb/typec/typec_wcove.c             |  373 ++++++++++
 include/linux/string.h                      |   10 +
 include/linux/usb/typec.h                   |  254 +++++++
 lib/string.c                                |   26 +
 12 files changed, 2042 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-class-typec
 create mode 100644 Documentation/usb/typec.txt
 create mode 100644 drivers/usb/typec/Kconfig
 create mode 100644 drivers/usb/typec/Makefile
 create mode 100644 drivers/usb/typec/typec.c
 create mode 100644 drivers/usb/typec/typec_wcove.c
 create mode 100644 include/linux/usb/typec.h

-- 
2.10.2

^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCHv13 1/3] lib/string: add sysfs_match_string helper
  2016-11-24 12:21 [PATCHv13 0/3] USB Type-C Connector class Heikki Krogerus
@ 2016-11-24 12:21 ` Heikki Krogerus
  2016-12-07 23:54   ` [PATCHv13,1/3] " Guenter Roeck
  2016-11-24 12:21 ` [PATCHv13 2/3] usb: USB Type-C connector class Heikki Krogerus
  2016-11-24 12:21 ` [PATCHv13 3/3] usb: typec: add driver for Intel Whiskey Cove PMIC USB Type-C PHY Heikki Krogerus
  2 siblings, 1 reply; 19+ messages in thread
From: Heikki Krogerus @ 2016-11-24 12:21 UTC (permalink / raw)
  To: Greg KH, Guenter Roeck; +Cc: Oliver Neukum, linux-kernel, linux-usb

Make a simple helper for matching strings with sysfs
attribute files. In most parts the same as match_string(),
except sysfs_match_string() uses sysfs_streq() instead of
strcmp() for matching. This is more convenient when used
with sysfs attributes.

Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Tested-by: Guenter Roeck <linux@roeck-us.net>
---
 include/linux/string.h | 10 ++++++++++
 lib/string.c           | 26 ++++++++++++++++++++++++++
 2 files changed, 36 insertions(+)

diff --git a/include/linux/string.h b/include/linux/string.h
index 26b6f6a..c4011b2 100644
--- a/include/linux/string.h
+++ b/include/linux/string.h
@@ -135,6 +135,16 @@ static inline int strtobool(const char *s, bool *res)
 }
 
 int match_string(const char * const *array, size_t n, const char *string);
+int __sysfs_match_string(const char * const *array, size_t n, const char *s);
+
+/**
+ * sysfs_match_string - matches given string in an array
+ * @_a: array of strings
+ * @_s: string to match with
+ *
+ * Helper for __sysfs_match_string(). Calculates the size of @a automatically.
+ */
+#define sysfs_match_string(_a, _s) __sysfs_match_string(_a, ARRAY_SIZE(_a), _s)
 
 #ifdef CONFIG_BINARY_PRINTF
 int vbin_printf(u32 *bin_buf, size_t size, const char *fmt, va_list args);
diff --git a/lib/string.c b/lib/string.c
index ed83562..c7a20cb 100644
--- a/lib/string.c
+++ b/lib/string.c
@@ -656,6 +656,32 @@ int match_string(const char * const *array, size_t n, const char *string)
 }
 EXPORT_SYMBOL(match_string);
 
+/**
+ * __sysfs_match_string - matches given string in an array
+ * @array: array of strings
+ * @n: number of strings in the array or -1 for NULL terminated arrays
+ * @str: string to match with
+ *
+ * Returns index of @str in the @array or -EINVAL, just like match_string().
+ * Uses sysfs_streq instead of strcmp for matching.
+ */
+int __sysfs_match_string(const char * const *array, size_t n, const char *str)
+{
+	const char *item;
+	int index;
+
+	for (index = 0; index < n; index++) {
+		item = array[index];
+		if (!item)
+			break;
+		if (!sysfs_streq(item, str))
+			return index;
+	}
+
+	return -EINVAL;
+}
+EXPORT_SYMBOL(__sysfs_match_string);
+
 #ifndef __HAVE_ARCH_MEMSET
 /**
  * memset - Fill a region of memory with the given value
-- 
2.10.2

^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCHv13 2/3] usb: USB Type-C connector class
  2016-11-24 12:21 [PATCHv13 0/3] USB Type-C Connector class Heikki Krogerus
  2016-11-24 12:21 ` [PATCHv13 1/3] lib/string: add sysfs_match_string helper Heikki Krogerus
@ 2016-11-24 12:21 ` Heikki Krogerus
  2016-11-29 16:27   ` Greg KH
  2016-11-24 12:21 ` [PATCHv13 3/3] usb: typec: add driver for Intel Whiskey Cove PMIC USB Type-C PHY Heikki Krogerus
  2 siblings, 1 reply; 19+ messages in thread
From: Heikki Krogerus @ 2016-11-24 12:21 UTC (permalink / raw)
  To: Greg KH, Guenter Roeck; +Cc: Oliver Neukum, linux-kernel, linux-usb

The purpose of USB Type-C connector class is to provide
unified interface for the user space to get the status and
basic information about USB Type-C connectors on a system,
control over data role swapping, and when the port supports
USB Power Delivery, also control over power role swapping
and Alternate Modes.

Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Tested-by: Guenter Roeck <linux@roeck-us.net>
---
 Documentation/ABI/testing/sysfs-class-typec |  220 ++++++
 Documentation/usb/typec.txt                 |  110 +++
 MAINTAINERS                                 |    9 +
 drivers/usb/Kconfig                         |    2 +
 drivers/usb/Makefile                        |    2 +
 drivers/usb/typec/Kconfig                   |    7 +
 drivers/usb/typec/Makefile                  |    1 +
 drivers/usb/typec/typec.c                   | 1013 +++++++++++++++++++++++++++
 include/linux/usb/typec.h                   |  254 +++++++
 9 files changed, 1618 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-class-typec
 create mode 100644 Documentation/usb/typec.txt
 create mode 100644 drivers/usb/typec/Kconfig
 create mode 100644 drivers/usb/typec/Makefile
 create mode 100644 drivers/usb/typec/typec.c
 create mode 100644 include/linux/usb/typec.h

diff --git a/Documentation/ABI/testing/sysfs-class-typec b/Documentation/ABI/testing/sysfs-class-typec
new file mode 100644
index 0000000..853fe2d
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-typec
@@ -0,0 +1,220 @@
+USB Type-C port devices (eg. /sys/class/typec/usbc0/)
+
+What:		/sys/class/typec/<port>/current_data_role
+Date:		December 2016
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		The current USB data role the port is operating in. 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.
+
+		Valid values:
+		- host
+		- device
+
+What:		/sys/class/typec/<port>/current_power_role
+Date:		December 2016
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		The current power role of the port. 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.
+
+		Valid values:
+		- source
+		- sink
+
+What:		/sys/class/typec/<port>/vconn_source
+Date:		December 2016
+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 are:
+		- 0 when the port is not the VCONN Source
+		- 1 when the port is the VCONN Source
+
+What:		/sys/class/typec/<port>/power_operation_mode
+Date:		December 2016
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows the current power operational mode the port is in.
+
+		Valid values:
+		- USB - Normal power levels defined in USB specifications
+		- USB Type-C 1.5A - Higher 1.5A current defined in USB Type-C
+				    specification.
+		- USB Type-C 3.0A - Higher 3A current defined in USB Type-C
+				    specification.
+                - USB Power Delivery - The voltages and currents defined in USB
+				       Power Delivery specification
+
+What:		/sys/class/typec/<port>/preferred_role
+Date:		December 2016
+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 there is no preferred role.
+
+		Valid values:
+		- source
+		- sink
+		- none - to remove preference
+
+What:		/sys/class/typec/<port>/supported_accessory_modes
+Date:		December 2016
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Lists the Accessory Modes, defined in the USB Type-C
+		specification, the port supports.
+
+What:		/sys/class/typec/<port>/supported_data_roles
+Date:		December 2016
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Lists the USB data roles the port is capable of supporting.
+
+		Valid values:
+		- device
+		- host
+		- device, host (DRD as defined in USB Type-C specification v1.2)
+
+What:		/sys/class/typec/<port>/supported_power_roles
+Date:		December 2016
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Lists the power roles the port is capable of supporting.
+
+		Valid values:
+		- source
+		- sink
+		- source, sink (DRP as defined in USB Type-C specification v1.2)
+
+What:		/sys/class/typec/<port>/supports_usb_power_delivery
+Date:		December 2016
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows if the port supports USB Power Delivery.
+		- 1 if USB Power Delivery is supported
+		- 0 when it's not
+
+
+USB Type-C partner devices (eg. /sys/class/typec/usbc0-partner/)
+
+What:		/sys/class/typec/<port>-partner/accessory_mode
+Date:		December 2016
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows the Accessory Mode name, or "no" when the partner does not
+		support Accesory Modes. The Accessory Modes are defined in USB
+		Type-C Specification.
+
+What:		/sys/class/typec/<port>-partner/supports_usb_power_delivery
+Date:		December 2016
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows if the partner supports USB Power Delivery:
+		- 0 when USB Power Delivery is not supported
+		- 1 when USB Power Delivery is supported
+
+
+USB Type-C cable devices (eg. /sys/class/typec/usbc0-cable/)
+
+Note: Electronically Marked Cables will have a device also for one cable plug
+(eg. /sys/class/typec/usbc0-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 their alternate modes
+as described in USB Type-C and USB Power Delivery specifications.
+
+What:		/sys/class/typec/<port>-cable/active
+Date:		December 2016
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows if the cable is active or passive.
+
+		Valid values:
+		- 0 when the cable is passive
+		- 1 when the cable is active
+
+What:		/sys/class/typec/<port>-cable/plug_type
+Date:		December 2016
+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 - USB Type-C
+		- Captive - Non-standard
+
+What:		/sys/class/typec/<port>-cable/supports_usb_power_delivery
+Date:		December 2016
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows if the cable supports USB Power Delivery:
+		- 0 when USB Power Delivery is not supported
+		- 1 when USB Power Delivery is supported
+
+
+Alternate Mode devices (For example,
+/sys/class/typec/usbc0-partner/usbc0-partner.svid:xxxx/). The ports, partners
+and cable plugs can have alternate modes.
+
+What:		/sys/class/typec/<dev>/<dev>.svid:<svid>/<mode>/active
+Date:		December 2016
+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:
+		- 0 when the mode is deactive
+		- 1 when the mode is active
+
+What:		/sys/class/typec/<dev>/<dev>.svid:<svid>/<mode>/description
+Date:		December 2016
+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/<dev>/<dev>.svid:<svid>/<mode>/vdo
+Date:		December 2016
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows the VDO in hexadecimal returned from the Discover Modes
+		command.
+
+What:		/sys/class/typec/<port>/<port>.svid:<svid>/<mode>/supported_roles
+Date:		December 2016
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows the roles, source or sink, the mode is supported with.
+
+		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.
diff --git a/Documentation/usb/typec.txt b/Documentation/usb/typec.txt
new file mode 100644
index 0000000..0787d50
--- /dev/null
+++ b/Documentation/usb/typec.txt
@@ -0,0 +1,110 @@
+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 the user space control
+over the roles and alternate modes they support 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.
+
+
+Interface
+---------
+Every port will be presented as its own device under /sys/class/typec/. The
+first port will be named "usbc0", the second "usbc1" 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. The
+partner attached to port "usbc0" will be named "usbc0-partner". Full path to the
+device would be /sys/class/typec/usb0/usb0-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 "usbc0" port
+will be named usbc0-cable and the plug on the SOP Prime end (see USB Power
+Delivery Specification ch. 2.4) will be named "usbc-plug0" and on the SOP Double
+Prime end "usbc0-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 support Alternate Modes, every Alternate Mode
+SVID will have their own device describing them. The Alternate Modes will not be
+attached to the typec class. For the port's "usbc0" partner, the Alternate Modes
+would have devices presented under /sys/class/typec/usbc0-partner/. Every mode
+that is supported will have its own group under the Alternate Mode device named
+"mode<id>". For example /sys/class/typec/usbc0/usbc0.svid:xxxx/mode0/. The
+requests for entering/exiting the modes happens with the "active" attribute in
+that group.
+
+
+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:
+
+struct typec_port *typec_register_port(struct device *dev,
+				       const struct typec_capability *cap);
+
+The class will provide handle to struct typec_port on success and ERR_PTR on
+failure. The un-registration of the port happens with the following API:
+
+void typec_unregister_port(struct typec_port *port);
+
+When registering the ports, the prefer_role member in struct typec_capability
+deservers 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.
+
+
+* Notifications
+
+When connection happens on a port, the port driver fills struct typec_connection
+which is passed to the class. The class provides the following API for reporting
+connection/disconnection:
+
+int typec_connect(struct typec_port *port, struct typec_connection *);
+void typec_disconnect(struct typec_port *);
+
+When the partner end has executed a role change, the port driver uses the
+following APIs to report it to the class:
+
+void typec_set_data_role(struct typec_port *, enum typec_data_role);
+void typec_set_pwr_role(struct typec_port *, enum typec_role);
+void typec_set_vconn_role(struct typec_port *, enum typec_role);
+void typec_set_pwr_opmode(struct typec_port *, enum typec_pwr_opmode);
+
+
+* Alternate Modes
+
+After connection, the port drivers register the alternate modes the partner
+and/or cable plugs support. And before reporting disconnection, the port driver
+_must_ unregister all the alternate modes registered for the partner and cable
+plugs. The API takes the struct device of the partner or the cable plug as
+parameter:
+
+int typec_register_altmodes(struct device *, struct typec_altmode *);
+void typec_unregister_altmodes(struct device *);
+
+When the partner end enters or exits the modes, the port driver needs to notify
+the class with the following API:
+
+void typec_altmode_update_active(struct typec_altmode *alt, int mode,
+				 bool active);
diff --git a/MAINTAINERS b/MAINTAINERS
index 1544655..15bbb6b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12795,6 +12795,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.txt
+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
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index fbe493d..89c322e 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -152,6 +152,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
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index 7791af6..c7f4098 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile
@@ -62,3 +62,5 @@ obj-$(CONFIG_USB_GADGET)	+= gadget/
 obj-$(CONFIG_USB_COMMON)	+= common/
 
 obj-$(CONFIG_USBIP_CORE)	+= usbip/
+
+obj-$(CONFIG_TYPEC)		+= typec/
diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
new file mode 100644
index 0000000..17792f9
--- /dev/null
+++ b/drivers/usb/typec/Kconfig
@@ -0,0 +1,7 @@
+
+menu "USB Power Delivery and Type-C drivers"
+
+config TYPEC
+	tristate
+
+endmenu
diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
new file mode 100644
index 0000000..1012a8b
--- /dev/null
+++ b/drivers/usb/typec/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_TYPEC)		+= typec.o
diff --git a/drivers/usb/typec/typec.c b/drivers/usb/typec/typec.c
new file mode 100644
index 0000000..eebaaa8
--- /dev/null
+++ b/drivers/usb/typec/typec.c
@@ -0,0 +1,1013 @@
+/*
+ * USB Type-C Connector Class
+ *
+ * Copyright (C) 2016, Intel Corporation
+ * Author: Heikki Krogerus <heikki.krogerus@linux.intel.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 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb/typec.h>
+
+struct typec_port {
+	unsigned int		id;
+	struct device		dev;
+
+	int			prefer_role;
+
+	enum typec_data_role	data_role;
+	enum typec_role		pwr_role;
+	enum typec_role		vconn_role;
+	enum typec_pwr_opmode	pwr_opmode;
+
+	struct typec_partner	*partner;
+	struct typec_cable	*cable;
+
+	unsigned int		connected:1;
+
+	const struct typec_capability *cap;
+};
+
+#define to_typec_port(p) container_of(p, struct typec_port, dev)
+#define to_typec_plug(p) container_of(p, struct typec_plug, dev)
+#define to_typec_cable(p) container_of(p, struct typec_cable, dev)
+#define to_typec_partner(p) container_of(p, struct typec_partner, dev)
+
+static DEFINE_IDA(typec_index_ida);
+
+static struct class typec_class = {
+	.name = "typec",
+};
+
+static const char * const typec_accessory_modes[] = {
+	[TYPEC_ACCESSORY_NONE]	= "no",
+	[TYPEC_ACCESSORY_AUDIO]	= "Audio Adapter Accessory Mode",
+	[TYPEC_ACCESSORY_DEBUG]	= "Debug Accessory Mode",
+};
+
+static struct device_type typec_partner_dev_type;
+static struct device_type typec_cable_dev_type;
+static struct device_type typec_port_dev_type;
+
+static ssize_t supports_usb_power_delivery_show(struct device *dev,
+						struct device_attribute *attr,
+						char *buf)
+{
+	if (dev->type == &typec_partner_dev_type) {
+		struct typec_partner *p = to_typec_partner(dev);
+
+		return sprintf(buf, "%d\n", p->usb_pd);
+	} else if (dev->type == &typec_partner_dev_type) {
+		struct typec_cable *p = to_typec_cable(dev);
+
+		return sprintf(buf, "%d\n", p->usb_pd);
+	} else {
+		struct typec_port *p = to_typec_port(dev);
+
+		return sprintf(buf, "%d\n", p->cap->usb_pd);
+	}
+}
+static DEVICE_ATTR_RO(supports_usb_power_delivery);
+
+/* ------------------------------------------------------------------------- */
+/* Type-C Partners */
+
+static ssize_t accessory_mode_show(struct device *dev,
+				   struct device_attribute *attr,
+				   char *buf)
+{
+	struct typec_partner *p = to_typec_partner(dev);
+
+	return sprintf(buf, "%s\n", typec_accessory_modes[p->accessory]);
+}
+static DEVICE_ATTR_RO(accessory_mode);
+
+static struct attribute *typec_partner_attrs[] = {
+	&dev_attr_accessory_mode.attr,
+	&dev_attr_supports_usb_power_delivery.attr,
+	NULL
+};
+ATTRIBUTE_GROUPS(typec_partner);
+
+static void typec_partner_release(struct device *dev)
+{
+	struct typec_port *port = to_typec_port(dev->parent);
+
+	port->partner = NULL;
+}
+
+static struct device_type typec_partner_dev_type = {
+	.name = "typec_partner_device",
+	.groups = typec_partner_groups,
+	.release = typec_partner_release,
+};
+
+static int
+typec_add_partner(struct typec_port *port, struct typec_partner *partner)
+{
+	struct device *dev = &partner->dev;
+	int ret;
+
+	dev->class = &typec_class;
+	dev->parent = &port->dev;
+	dev->type = &typec_partner_dev_type;
+	dev_set_name(dev, "%s-partner", dev_name(&port->dev));
+
+	ret = device_register(dev);
+	if (ret) {
+		put_device(dev);
+		return ret;
+	}
+
+	port->partner = partner;
+	return 0;
+}
+
+static void typec_remove_partner(struct typec_port *port)
+{
+	device_unregister(&port->partner->dev);
+}
+
+/* ------------------------------------------------------------------------- */
+/* Type-C Cable Plugs */
+
+static void typec_plug_release(struct device *dev)
+{
+	struct typec_plug *plug = to_typec_plug(dev);
+
+	memset(plug, 0, sizeof(*plug));
+}
+
+static struct device_type typec_plug_dev_type = {
+	.name = "typec_plug_device",
+	.release = typec_plug_release,
+};
+
+static int
+typec_add_plug(struct typec_port *port, struct typec_plug *plug)
+{
+	struct device *dev = &plug->dev;
+	char name[8];
+	int ret;
+
+	sprintf(name, "plug%d", plug->index);
+
+	dev->class = &typec_class;
+	dev->parent = &port->cable->dev;
+	dev->type = &typec_plug_dev_type;
+	dev_set_name(dev, "%s-%s", dev_name(&port->dev), name);
+
+	ret = device_register(dev);
+	if (ret) {
+		put_device(dev);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void typec_remove_plug(struct typec_plug *plug)
+{
+	device_unregister(&plug->dev);
+}
+
+/* Type-C Cables */
+
+static ssize_t
+active_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct typec_cable *cable = to_typec_cable(dev);
+
+	return sprintf(buf, "%d\n", cable->active);
+}
+static DEVICE_ATTR_RO(active);
+
+static const char * const typec_plug_types[] = {
+	[USB_PLUG_NONE]		= "unknown",
+	[USB_PLUG_TYPE_A]	= "Type-A",
+	[USB_PLUG_TYPE_B]	= "Type-B",
+	[USB_PLUG_TYPE_C]	= "Type-C",
+	[USB_PLUG_CAPTIVE]	= "Captive",
+};
+
+static ssize_t plug_type_show(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct typec_cable *cable = to_typec_cable(dev);
+
+	return sprintf(buf, "%s\n", typec_plug_types[cable->type]);
+}
+static DEVICE_ATTR_RO(plug_type);
+
+static struct attribute *typec_cable_attrs[] = {
+	&dev_attr_active.attr,
+	&dev_attr_plug_type.attr,
+	&dev_attr_supports_usb_power_delivery.attr,
+	NULL
+};
+ATTRIBUTE_GROUPS(typec_cable);
+
+static void typec_cable_release(struct device *dev)
+{
+	struct typec_port *port = to_typec_port(dev->parent);
+
+	port->cable = NULL;
+}
+
+static struct device_type typec_cable_dev_type = {
+	.name = "typec_cable_device",
+	.groups = typec_cable_groups,
+	.release = typec_cable_release,
+};
+
+static int typec_add_cable(struct typec_port *port, struct typec_cable *cable)
+{
+	struct device *dev = &cable->dev;
+	int ret;
+
+	dev->class = &typec_class;
+	dev->parent = &port->dev;
+	dev->type = &typec_cable_dev_type;
+	dev_set_name(dev, "%s-cable", dev_name(&port->dev));
+
+	ret = device_register(dev);
+	if (ret) {
+		put_device(dev);
+		return ret;
+	}
+
+	/* Plug1 */
+	if (!cable->usb_pd)
+		return 0;
+
+	cable->plug[0].index = 1;
+	ret = typec_add_plug(port, &cable->plug[0]);
+	if (ret) {
+		device_unregister(dev);
+		return ret;
+	}
+
+	/* Plug2 */
+	if (!cable->active || !cable->sop_pp_controller)
+		return 0;
+
+	cable->plug[1].index = 2;
+	ret = typec_add_plug(port, &cable->plug[1]);
+	if (ret) {
+		typec_remove_plug(&cable->plug[0]);
+		device_unregister(dev);
+		return ret;
+	}
+
+	port->cable = cable;
+	return 0;
+}
+
+static void typec_remove_cable(struct typec_port *port)
+{
+	if (port->cable->active) {
+		typec_remove_plug(&port->cable->plug[0]);
+		if (port->cable->sop_pp_controller)
+			typec_remove_plug(&port->cable->plug[1]);
+	}
+	device_unregister(&port->cable->dev);
+}
+
+/* ------------------------------------------------------------------------- */
+/* API for the port drivers */
+
+static void typec_init_roles(struct typec_port *port)
+{
+	if (port->prefer_role < 0)
+		return;
+
+	if (port->prefer_role == TYPEC_SOURCE) {
+		port->data_role = TYPEC_HOST;
+		port->pwr_role = TYPEC_SOURCE;
+		port->vconn_role = TYPEC_SOURCE;
+	} else {
+		/* Device mode as default also by default with DRP ports */
+		port->data_role = TYPEC_DEVICE;
+		port->pwr_role = TYPEC_SINK;
+		port->vconn_role = TYPEC_SINK;
+	}
+}
+
+int typec_connect(struct typec_port *port, struct typec_connection *con)
+{
+	int ret;
+
+	if (!con->partner && !con->cable)
+		return -EINVAL;
+
+	port->connected = 1;
+	port->data_role = con->data_role;
+	port->pwr_role = con->pwr_role;
+	port->vconn_role = con->vconn_role;
+	port->pwr_opmode = con->pwr_opmode;
+
+	if (con->cable) {
+		ret = typec_add_cable(port, con->cable);
+		if (ret)
+			return ret;
+	}
+
+	if (con->partner) {
+		ret = typec_add_partner(port, con->partner);
+		if (ret) {
+			if (con->cable)
+				typec_remove_cable(port);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(typec_connect);
+
+void typec_disconnect(struct typec_port *port)
+{
+	if (port->partner)
+		typec_remove_partner(port);
+
+	if (port->cable)
+		typec_remove_cable(port);
+
+	port->connected = 0;
+
+	port->pwr_opmode = TYPEC_PWR_MODE_USB;
+
+	typec_init_roles(port);
+}
+EXPORT_SYMBOL_GPL(typec_disconnect);
+
+/* --------------------------------------- */
+/* Driver callbacks to report role updates */
+
+void typec_set_data_role(struct typec_port *port, enum typec_data_role role)
+{
+	port->data_role = role;
+	sysfs_notify(&port->dev.kobj, NULL, "current_data_role");
+	kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
+}
+EXPORT_SYMBOL_GPL(typec_set_data_role);
+
+void typec_set_pwr_role(struct typec_port *port, enum typec_role role)
+{
+	port->pwr_role = role;
+	sysfs_notify(&port->dev.kobj, NULL, "current_power_role");
+	kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
+}
+EXPORT_SYMBOL_GPL(typec_set_pwr_role);
+
+void typec_set_vconn_role(struct typec_port *port, enum typec_role role)
+{
+	port->vconn_role = role;
+	sysfs_notify(&port->dev.kobj, NULL, "current_vconn_role");
+	kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
+}
+EXPORT_SYMBOL_GPL(typec_set_vconn_role);
+
+void typec_set_pwr_opmode(struct typec_port *port,
+			  enum typec_pwr_opmode opmode)
+{
+	port->pwr_opmode = opmode;
+	sysfs_notify(&port->dev.kobj, NULL, "power_operation_mode");
+	kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
+}
+EXPORT_SYMBOL_GPL(typec_set_pwr_opmode);
+
+/* -------------------------------- */
+/* Alternate Modes */
+
+/*
+ * typec_altmode_update_active - Notify about Enter/Exit mode
+ * @alt: Handle to the Alternate Mode
+ * @mode: Mode id
+ * @active: True when the mode has been enterred
+ */
+void typec_altmode_update_active(struct typec_altmode *alt, int mode,
+				 bool active)
+{
+	struct typec_mode *m = alt->modes + mode;
+	char dir[6];
+
+	m->active = active;
+	snprintf(dir, 6, "mode%d", mode);
+	sysfs_notify(&alt->dev.kobj, dir, "active");
+	kobject_uevent(&alt->dev.kobj, KOBJ_CHANGE);
+}
+EXPORT_SYMBOL_GPL(typec_altmode_update_active);
+
+/*
+ * typec_altmode2port - Alternate Mode to USB Type-C port
+ * @alt: The Alternate Mode
+ *
+ * Returns the port that the cable plug or partner with @alt is connected to.
+ */
+struct typec_port *typec_altmode2port(struct typec_altmode *alt)
+{
+	if (alt->dev.parent->type == &typec_plug_dev_type)
+		return to_typec_port(alt->dev.parent->parent->parent);
+	if (alt->dev.parent->type == &typec_partner_dev_type)
+		return to_typec_port(alt->dev.parent->parent);
+	if (alt->dev.parent->type == &typec_port_dev_type)
+		return to_typec_port(alt->dev.parent);
+
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(typec_altmode2port);
+
+static void typec_altmode_release(struct device *dev)
+{
+	struct typec_altmode *alt = to_altmode(dev);
+
+	kfree(alt->mode_groups);
+}
+
+static ssize_t
+typec_altmode_vdo_show(struct device *dev, struct device_attribute *attr,
+		       char *buf)
+{
+	struct typec_mode *mode = container_of(attr, struct typec_mode,
+					       vdo_attr);
+
+	return sprintf(buf, "0x%08x\n", mode->vdo);
+}
+
+static ssize_t
+typec_altmode_desc_show(struct device *dev, struct device_attribute *attr,
+			char *buf)
+{
+	struct typec_mode *mode = container_of(attr, struct typec_mode,
+					       desc_attr);
+
+	return sprintf(buf, "%s\n", mode->desc ? mode->desc : "");
+}
+
+static ssize_t
+typec_altmode_active_show(struct device *dev, struct device_attribute *attr,
+			  char *buf)
+{
+	struct typec_mode *mode = container_of(attr, struct typec_mode,
+					       active_attr);
+
+	return sprintf(buf, "%d\n", mode->active);
+}
+
+static ssize_t
+typec_altmode_active_store(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t size)
+{
+	struct typec_mode *mode = container_of(attr, struct typec_mode,
+					       active_attr);
+	struct typec_port *port = typec_altmode2port(mode->alt_mode);
+	bool activate;
+	int ret;
+
+	if (!port->cap->activate_mode)
+		return -EOPNOTSUPP;
+
+	ret = kstrtobool(buf, &activate);
+	if (ret)
+		return ret;
+
+	ret = port->cap->activate_mode(mode->alt_mode, mode->index, activate);
+	if (ret)
+		return ret;
+
+	return size;
+}
+
+static ssize_t
+typec_altmode_roles_show(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct typec_mode *mode = container_of(attr, struct typec_mode,
+					       roles_attr);
+	ssize_t ret;
+
+	switch (mode->roles) {
+	case TYPEC_PORT_DFP:
+		ret =  sprintf(buf, "source\n");
+		break;
+	case TYPEC_PORT_UFP:
+		ret = sprintf(buf, "sink\n");
+		break;
+	case TYPEC_PORT_DRP:
+	default:
+		ret = sprintf(buf, "source, sink\n");
+		break;
+	}
+	return ret;
+}
+
+static void typec_init_modes(struct typec_altmode *alt, int is_port)
+{
+	struct typec_mode *mode = alt->modes;
+	int i;
+
+	for (i = 0; i < alt->n_modes; i++, mode++) {
+		mode->alt_mode = alt;
+		mode->index = i;
+		sprintf(mode->group_name, "mode%d", i);
+
+		sysfs_attr_init(&mode->vdo_attr.attr);
+		mode->vdo_attr.attr.name = "vdo";
+		mode->vdo_attr.attr.mode = 0444;
+		mode->vdo_attr.show = typec_altmode_vdo_show;
+
+		sysfs_attr_init(&mode->desc_attr.attr);
+		mode->desc_attr.attr.name = "description";
+		mode->desc_attr.attr.mode = 0444;
+		mode->desc_attr.show = typec_altmode_desc_show;
+
+		sysfs_attr_init(&mode->active_attr.attr);
+		mode->active_attr.attr.name = "active";
+		mode->active_attr.attr.mode = 0644;
+		mode->active_attr.show = typec_altmode_active_show;
+		mode->active_attr.store = typec_altmode_active_store;
+
+		mode->attrs[0] = &mode->vdo_attr.attr;
+		mode->attrs[1] = &mode->desc_attr.attr;
+		mode->attrs[2] = &mode->active_attr.attr;
+
+		/* With ports, list the roles that the mode is supported with */
+		if (is_port) {
+			sysfs_attr_init(&mode->roles_attr.attr);
+			mode->roles_attr.attr.name = "supported_roles";
+			mode->roles_attr.attr.mode = 0444;
+			mode->roles_attr.show = typec_altmode_roles_show;
+
+			mode->attrs[3] = &mode->roles_attr.attr;
+		}
+
+		mode->group.attrs = mode->attrs;
+		mode->group.name = mode->group_name;
+
+		alt->mode_groups[i] = &mode->group;
+	}
+}
+
+static int
+typec_add_altmode(struct device *parent, struct typec_altmode *alt, int is_port)
+{
+	struct device *dev = &alt->dev;
+	int ret;
+
+	alt->mode_groups = kcalloc(alt->n_modes + 1,
+				   sizeof(struct attribute_group *),
+				   GFP_KERNEL);
+	if (!alt->mode_groups)
+		return -ENOMEM;
+
+	typec_init_modes(alt, is_port);
+
+	dev->groups = alt->mode_groups;
+	dev->release = typec_altmode_release;
+	dev->parent = parent;
+	/* TODO: if (!is_port) dev->bus = &typec_altmode_bus; */
+
+	dev_set_name(dev, "%s.svid:%04x", dev_name(parent), alt->svid);
+
+	ret = device_register(dev);
+	if (ret) {
+		put_device(dev);
+		kfree(alt->mode_groups);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int __typec_register_altmodes(struct device *parent,
+				     struct typec_altmode *alt_modes,
+				     int is_port)
+{
+	struct typec_altmode *alt;
+	int index;
+	int ret;
+
+	if (!alt_modes)
+		return 0;
+
+	for (alt = alt_modes, index = 0; alt->svid; alt++, index++) {
+		ret = typec_add_altmode(parent, alt, is_port);
+		if (ret)
+			goto err;
+	}
+
+	return 0;
+err:
+	for (alt = alt_modes + index; index; alt--, index--)
+		device_unregister(&alt->dev);
+
+	return ret;
+}
+
+int typec_register_altmodes(struct device *dev, struct typec_altmode *alt_modes)
+{
+	if (dev->type == &typec_partner_dev_type) {
+		struct typec_partner *p = to_typec_partner(dev);
+
+		p->alt_modes = alt_modes;
+	} else if (dev->type == &typec_plug_dev_type) {
+		struct typec_plug *p = to_typec_plug(dev);
+
+		p->alt_modes = alt_modes;
+	} else {
+		return -ENODEV;
+	}
+
+	return __typec_register_altmodes(dev, alt_modes, false);
+}
+EXPORT_SYMBOL_GPL(typec_register_altmodes);
+
+void typec_unregister_altmodes(struct device *dev)
+{
+	struct typec_altmode *alt_modes = NULL;
+	struct typec_altmode *alt;
+
+	if (dev->type == &typec_partner_dev_type) {
+		struct typec_partner *p = to_typec_partner(dev);
+
+		alt_modes = p->alt_modes;
+		p->alt_modes = NULL;
+	} else if (dev->type == &typec_plug_dev_type) {
+		struct typec_plug *p = to_typec_plug(dev);
+
+		alt_modes = p->alt_modes;
+		p->alt_modes = NULL;
+	}
+
+	if (!alt_modes)
+		return;
+
+	for (alt = alt_modes; alt->svid; alt++)
+		device_unregister(&alt->dev);
+}
+EXPORT_SYMBOL_GPL(typec_unregister_altmodes);
+
+/* ------------------------------------------------------------------------- */
+/* USB Type-C ports */
+
+static const char * const typec_roles[] = {
+	[TYPEC_SINK]	= "sink",
+	[TYPEC_SOURCE]	= "source",
+};
+
+static const char * const typec_data_roles[] = {
+	[TYPEC_DEVICE]	= "device",
+	[TYPEC_HOST]	= "host",
+};
+
+static ssize_t
+preferred_role_store(struct device *dev, struct device_attribute *attr,
+		     const char *buf, size_t size)
+{
+	struct typec_port *port = to_typec_port(dev);
+	int role;
+	int ret;
+
+	if (port->cap->type != TYPEC_PORT_DRP) {
+		dev_dbg(dev, "Preferred role only supported with DRP ports\n");
+		return -EOPNOTSUPP;
+	}
+
+	if (!port->cap->try_role) {
+		dev_dbg(dev, "Setting preferred role not supported\n");
+		return -EOPNOTSUPP;
+	}
+
+	role = sysfs_match_string(typec_roles, buf);
+	if (role < 0) {
+		if (sysfs_streq(buf, "none"))
+			role = TYPEC_NO_PREFERRED_ROLE;
+		else
+			return -EINVAL;
+	}
+
+	ret = port->cap->try_role(port->cap, role);
+	if (ret)
+		return ret;
+
+	port->prefer_role = role;
+	return size;
+}
+
+static ssize_t
+preferred_role_show(struct device *dev, struct device_attribute *attr,
+		    char *buf)
+{
+	struct typec_port *port = to_typec_port(dev);
+
+	if (port->prefer_role < 0)
+		return 0;
+
+	return sprintf(buf, "%s\n", typec_roles[port->prefer_role]);
+}
+static DEVICE_ATTR_RW(preferred_role);
+
+static ssize_t
+current_data_role_store(struct device *dev, struct device_attribute *attr,
+			const char *buf, size_t size)
+{
+	struct typec_port *port = to_typec_port(dev);
+	int ret;
+
+	if (port->cap->type != TYPEC_PORT_DRP) {
+		dev_dbg(dev, "data role swap only supported with DRP ports\n");
+		return -EOPNOTSUPP;
+	}
+
+	if (!port->cap->dr_set) {
+		dev_dbg(dev, "data role swapping not supported\n");
+		return -EOPNOTSUPP;
+	}
+
+	ret = sysfs_match_string(typec_data_roles, buf);
+	if (ret < 0)
+		return ret;
+
+	ret = port->cap->dr_set(port->cap, ret);
+	if (ret)
+		return ret;
+
+	return size;
+}
+
+static ssize_t
+current_data_role_show(struct device *dev, struct device_attribute *attr,
+		       char *buf)
+{
+	struct typec_port *port = to_typec_port(dev);
+
+	return sprintf(buf, "%s\n", typec_data_roles[port->data_role]);
+}
+static DEVICE_ATTR_RW(current_data_role);
+
+static ssize_t supported_data_roles_show(struct device *dev,
+					 struct device_attribute *attr,
+					 char *buf)
+{
+	struct typec_port *port = to_typec_port(dev);
+
+	if (port->cap->type == TYPEC_PORT_DRP)
+		return sprintf(buf, "host, device\n");
+
+	return sprintf(buf, "%s\n", typec_data_roles[port->data_role]);
+}
+static DEVICE_ATTR_RO(supported_data_roles);
+
+static ssize_t current_power_role_store(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t size)
+{
+	struct typec_port *port = to_typec_port(dev);
+	int ret = size;
+
+	if (!port->cap->usb_pd) {
+		dev_dbg(dev, "power role swap only supported with USB PD\n");
+		return -EOPNOTSUPP;
+	}
+
+	if (!port->cap->pr_set) {
+		dev_dbg(dev, "power role swapping not supported\n");
+		return -EOPNOTSUPP;
+	}
+
+	if (port->pwr_opmode != TYPEC_PWR_MODE_PD) {
+		dev_dbg(dev, "partner unable to swap power role\n");
+		return -EIO;
+	}
+
+	ret = sysfs_match_string(typec_roles, buf);
+	if (ret < 0)
+		return ret;
+
+	ret = port->cap->pr_set(port->cap, ret);
+	if (ret)
+		return ret;
+
+	return size;
+}
+
+static ssize_t current_power_role_show(struct device *dev,
+				       struct device_attribute *attr, char *buf)
+{
+	struct typec_port *port = to_typec_port(dev);
+
+	return sprintf(buf, "%s\n", typec_roles[port->pwr_role]);
+}
+static DEVICE_ATTR_RW(current_power_role);
+
+static ssize_t supported_power_roles_show(struct device *dev,
+					  struct device_attribute *attr,
+					  char *buf)
+{
+	struct typec_port *port = to_typec_port(dev);
+
+	if (port->cap->usb_pd || port->cap->type == TYPEC_PORT_DRP)
+		return sprintf(buf, "source, sink\n");
+
+	return sprintf(buf, "%s\n", typec_roles[port->pwr_role]);
+}
+static DEVICE_ATTR_RO(supported_power_roles);
+
+static const char * const typec_pwr_opmodes[] = {
+	[TYPEC_PWR_MODE_USB]	= "USB",
+	[TYPEC_PWR_MODE_1_5A]	= "USB Type-C 1.5A",
+	[TYPEC_PWR_MODE_3_0A]	= "USB Type-C 3.0A",
+	[TYPEC_PWR_MODE_PD]	= "USB Power Delivery",
+};
+
+static ssize_t power_operation_mode_show(struct device *dev,
+					 struct device_attribute *attr,
+					 char *buf)
+{
+	struct typec_port *port = to_typec_port(dev);
+
+	return sprintf(buf, "%s\n", typec_pwr_opmodes[port->pwr_opmode]);
+}
+static DEVICE_ATTR_RO(power_operation_mode);
+
+static ssize_t vconn_source_store(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t size)
+{
+	struct typec_port *port = to_typec_port(dev);
+	enum typec_role role;
+	int ret;
+
+	if (!port->cap->usb_pd) {
+		dev_dbg(dev, "vconn swap only supported with USB PD\n");
+		return -EOPNOTSUPP;
+	}
+
+	if (!port->cap->vconn_set) {
+		dev_dbg(dev, "vconn swapping not supported\n");
+		return -EOPNOTSUPP;
+	}
+
+	if (sysfs_streq(buf, "1"))
+		role = TYPEC_SOURCE;
+	else if (sysfs_streq(buf, "0"))
+		role = TYPEC_SINK;
+	else
+		return -EINVAL;
+
+	ret = port->cap->vconn_set(port->cap, role);
+	if (ret)
+		return ret;
+
+	return size;
+}
+
+static ssize_t vconn_source_show(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct typec_port *port = to_typec_port(dev);
+
+	return sprintf(buf, "%d\n", port->vconn_role == TYPEC_SOURCE ? 1 : 0);
+}
+static DEVICE_ATTR_RW(vconn_source);
+
+static ssize_t supported_accessory_modes_show(struct device *dev,
+					      struct device_attribute *attr,
+					      char *buf)
+{
+	struct typec_port *port = to_typec_port(dev);
+	ssize_t ret = 0;
+	int i;
+
+	if (!port->cap->accessory)
+		return 0;
+
+	for (i = 0; port->cap->accessory[i]; i++)
+		ret += sprintf(buf + ret, "%s\n",
+			       typec_accessory_modes[port->cap->accessory[i]]);
+	return ret;
+}
+static DEVICE_ATTR_RO(supported_accessory_modes);
+
+static struct attribute *typec_attrs[] = {
+	&dev_attr_current_power_role.attr,
+	&dev_attr_current_data_role.attr,
+	&dev_attr_power_operation_mode.attr,
+	&dev_attr_preferred_role.attr,
+	&dev_attr_supported_accessory_modes.attr,
+	&dev_attr_supported_data_roles.attr,
+	&dev_attr_supported_power_roles.attr,
+	&dev_attr_supports_usb_power_delivery.attr,
+	&dev_attr_vconn_source.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(typec);
+
+static int typec_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+	int ret;
+
+	ret = add_uevent_var(env, "TYPEC_PORT=%s", dev_name(dev));
+	if (ret)
+		dev_err(dev, "failed to add uevent TYPEC_PORT\n");
+
+	return ret;
+}
+
+static void typec_release(struct device *dev)
+{
+	struct typec_port *port = to_typec_port(dev);
+
+	ida_simple_remove(&typec_index_ida, port->id);
+	kfree(port);
+}
+
+static struct device_type typec_port_dev_type = {
+	.name = "typec_port",
+	.groups = typec_groups,
+	.uevent = typec_uevent,
+	.release = typec_release,
+};
+
+struct typec_port *typec_register_port(struct device *dev,
+				       const struct typec_capability *cap)
+{
+	struct typec_port *port;
+	int ret;
+	int id;
+
+	port = kzalloc(sizeof(*port), GFP_KERNEL);
+	if (!port)
+		return ERR_PTR(-ENOMEM);
+
+	id = ida_simple_get(&typec_index_ida, 0, 0, GFP_KERNEL);
+	if (id < 0) {
+		kfree(port);
+		return ERR_PTR(id);
+	}
+
+	port->prefer_role = cap->prefer_role;
+
+	port->id = id;
+	port->cap = cap;
+	port->dev.type = &typec_port_dev_type;
+	port->dev.class = &typec_class;
+	port->dev.parent = dev;
+	dev_set_name(&port->dev, "usbc%d", id);
+
+	typec_init_roles(port);
+
+	ret = device_register(&port->dev);
+	if (ret)
+		goto reg_err;
+
+	ret = __typec_register_altmodes(&port->dev, cap->alt_modes, true);
+	if (ret)
+		goto alt_err;
+
+	return port;
+alt_err:
+	device_unregister(&port->dev);
+reg_err:
+	ida_simple_remove(&typec_index_ida, id);
+	put_device(&port->dev);
+	kfree(port);
+	return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(typec_register_port);
+
+void typec_unregister_port(struct typec_port *port)
+{
+	struct typec_altmode *alt;
+
+	WARN_ON(port->connected);
+
+	if (port->cap->alt_modes)
+		for (alt = port->cap->alt_modes; alt->svid; alt++)
+			device_unregister(&alt->dev);
+	device_unregister(&port->dev);
+}
+EXPORT_SYMBOL_GPL(typec_unregister_port);
+
+static int __init typec_init(void)
+{
+	return class_register(&typec_class);
+}
+subsys_initcall(typec_init);
+
+static void __exit typec_exit(void)
+{
+	ida_destroy(&typec_index_ida);
+	class_unregister(&typec_class);
+}
+module_exit(typec_exit);
+
+MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("USB Type-C Connector Class");
diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
new file mode 100644
index 0000000..1628bc6
--- /dev/null
+++ b/include/linux/usb/typec.h
@@ -0,0 +1,254 @@
+
+#ifndef __LINUX_USB_TYPEC_H
+#define __LINUX_USB_TYPEC_H
+
+#include <linux/device.h>
+#include <linux/types.h>
+
+struct typec_port;
+
+enum typec_port_type {
+	TYPEC_PORT_DFP,
+	TYPEC_PORT_UFP,
+	TYPEC_PORT_DRP,
+};
+
+enum typec_plug_type {
+	USB_PLUG_NONE,
+	USB_PLUG_TYPE_A,
+	USB_PLUG_TYPE_B,
+	USB_PLUG_TYPE_C,
+	USB_PLUG_CAPTIVE,
+};
+
+enum typec_data_role {
+	TYPEC_DEVICE,
+	TYPEC_HOST,
+};
+
+enum typec_role {
+	TYPEC_SINK,
+	TYPEC_SOURCE,
+};
+
+enum typec_pwr_opmode {
+	TYPEC_PWR_MODE_USB,
+	TYPEC_PWR_MODE_1_5A,
+	TYPEC_PWR_MODE_3_0A,
+	TYPEC_PWR_MODE_PD,
+};
+
+enum typec_accessory {
+	TYPEC_ACCESSORY_NONE,
+	TYPEC_ACCESSORY_AUDIO,
+	TYPEC_ACCESSORY_DEBUG,
+};
+
+/*
+ * struct typec_mode - Individual Mode of an Alternate Mode
+ * @vdo: VDO returned by Discover Modes USB PD command
+ * @desc: Mode description
+ * @active: Tells if the mode is currently entered or not
+ * @index: Index of the mode
+ * @group_name: Name for the sysfs folder in form "mode<index>"
+ * @group: The sysfs group (folder) for the mode
+ * @attrs: The attributes for the sysfs group
+ * @vdo_attr: Device attribute to expose the VDO of the mode
+ * @desc_attr: Device attribute to expose the description of the mode
+ * @active_attr: Device attribute to expose active of the mode
+ * @roles: Only for ports. DRP if the mode is awailable in both roles
+ * @roles_attr: Device attribute, only for ports, to expose the supported roles
+ *
+ * Details about a mode of an Alternate Mode which a connector, cable plug or
+ * partner supports. Every mode will have it's own sysfs group. The details are
+ * the VDO returned by discover modes command, description for the mode and
+ * active flag telling is the mode currently active or not.
+ */
+struct typec_mode {
+	u32			vdo;
+	char			*desc;
+	unsigned int		active:1;
+	/* Only for ports */
+	enum typec_port_type	roles;
+
+	struct typec_altmode	*alt_mode;
+
+	int			index;
+	char			group_name[8];
+	struct attribute_group	group;
+	struct attribute	*attrs[5];
+	struct device_attribute vdo_attr;
+	struct device_attribute desc_attr;
+	struct device_attribute active_attr;
+	/* Only for ports */
+	struct device_attribute roles_attr;
+};
+
+/*
+ * struct typec_altmode - USB Type-C Alternate Mode
+ * @dev: struct device instance
+ * @name: Name for the Alternate Mode (optional)
+ * @svid: Standard or Vendor ID
+ * @n_modes: Number of modes
+ * @modes: Array of modes supported by the Alternat Mode
+ * @mode_groups: The modes as attribute groups to be exposed in sysfs
+ *
+ * Representation of an Alternate Mode that has SVID assigned by USB-IF. The
+ * array of modes will list the modes of a particular SVID that are supported by
+ * a connector, partner of a cable plug.
+ */
+struct typec_altmode {
+	struct device		dev;
+	char			*name;
+
+	u16			svid;
+	int			n_modes;
+	struct typec_mode	*modes;
+
+	const struct attribute_group **mode_groups;
+};
+
+#define to_altmode(d) container_of(d, struct typec_altmode, dev)
+
+struct typec_port *typec_altmode2port(struct typec_altmode *alt);
+
+void typec_altmode_update_active(struct typec_altmode *alt, int mode,
+				 bool active);
+
+int typec_register_altmodes(struct device *dev, struct typec_altmode *alt);
+void typec_unregister_altmodes(struct device *dev);
+
+/*
+ * struct typec_plug - USB Type-C Cable Plug
+ * @dev: struct device instance
+ * @index: 1 for the plug connected to DFP and 2 for the plug connected to UFP
+ * @alt_modes: Alternate Modes the cable plug supports (null terminated)
+ *
+ * Represents USB Type-C Cable Plug.
+ */
+struct typec_plug {
+	struct device		dev;
+	int			index;
+	struct typec_altmode	*alt_modes;
+};
+
+/*
+ * struct typec_cable - USB Type-C Cable
+ * @dev: struct device instance
+ * @type: The plug type from USB PD Cable VDO
+ * @usb_pd: Electronically Marked Cable
+ * @active: Is the cable active or passive
+ * @sop_pp_controller: Tells whether both cable plugs are configurable or not
+ * @plug: The two plugs in the cable.
+ *
+ * Represents USB Type-C Cable attached to USB Type-C port. Two plugs are
+ * created if the cable has SOP Double Prime controller as defined in USB PD
+ * specification. Otherwise only one will be created if the cable is active. For
+ * passive cables no plugs are created.
+ */
+struct typec_cable {
+	struct device		dev;
+	enum typec_plug_type	type;
+	u32			vdo;
+	unsigned int		usb_pd:1;
+	unsigned int		active:1;
+	unsigned int		sop_pp_controller:1;
+
+	struct typec_plug	plug[2];
+};
+
+/*
+ * struct typec_partner - USB Type-C Partner
+ * @dev: struct device instance
+ * @type: Normal USB device, charger, Alternate Mode or Accessory
+ * @usb_pd: USB Power Delivery support
+ * @vdo: VDO returned by Discover Identity USB PD command
+ * @alt_modes: Alternate Modes the partner supports (null terminated)
+ *
+ * Details about a partner that is attached to USB Type-C port.
+ */
+struct typec_partner {
+	struct device		dev;
+	unsigned int		usb_pd:1;
+	u32			vdo;
+	enum typec_accessory	accessory;
+	struct typec_altmode	*alt_modes;
+};
+
+/*
+ * struct typec_capability - USB Type-C Port Capabilities
+ * @role: DFP (Host-only), UFP (Device-only) or DRP (Dual Role)
+ * @usb_pd: USB Power Delivery support
+ * @prefer_role: Initial role preference
+ * @accessory: Supported Accessory Modes (NULL terminated array)
+ * @alt_modes: Alternate Modes the connector supports (NULL terminated)
+ * @try_role: Set data role preference for DRP port
+ * @dr_set: Set Data Role
+ * @pr_set: Set Power Role
+ * @vconn_set: Set VCONN Role
+ * @activate_mode: Enter/exit given Alternate Mode
+ *
+ * Static capabilities of a single USB Type-C port.
+ */
+struct typec_capability {
+	enum typec_port_type	type;
+	unsigned int		usb_pd:1;
+	int			prefer_role;
+	enum typec_accessory	*accessory;
+	struct typec_altmode	*alt_modes;
+
+	int			(*try_role)(const struct typec_capability *,
+					    int role);
+
+	int			(*dr_set)(const struct typec_capability *,
+					  enum typec_data_role);
+	int			(*pr_set)(const struct typec_capability *,
+					  enum typec_role);
+	int			(*vconn_set)(const struct typec_capability *,
+					     enum typec_role);
+
+	int			(*activate_mode)(struct typec_altmode *,
+						 int mode, int activate);
+};
+
+/* Specific to try_role(). Indicates the user want's to clear the preference. */
+#define TYPEC_NO_PREFERRED_ROLE	(-1)
+
+/*
+ * struct typec_connection - Details about USB Type-C port connection event
+ * @partner: The attached partner
+ * @cable: The attached cable
+ * @data_role: Initial USB data role (host or device)
+ * @pwr_role: Initial Power role (source or sink)
+ * @vconn_role: Initial VCONN role (source or sink)
+ * @pwr_opmode: The power mode of the connection
+ *
+ * All the relevant details about a connection event. Wrapper that is passed to
+ * typec_connect(). The context is copied when typec_connect() is called and the
+ * structure is not used for anything else.
+ */
+struct typec_connection {
+	struct typec_partner	*partner;
+	struct typec_cable	*cable;
+
+	enum typec_data_role	data_role;
+	enum typec_role		pwr_role;
+	enum typec_role		vconn_role;
+	enum typec_pwr_opmode	pwr_opmode;
+};
+
+struct typec_port *typec_register_port(struct device *dev,
+				       const struct typec_capability *cap);
+void typec_unregister_port(struct typec_port *port);
+
+int typec_connect(struct typec_port *port, struct typec_connection *con);
+void typec_disconnect(struct typec_port *port);
+
+/* Callbacks from driver */
+
+void typec_set_data_role(struct typec_port *port, enum typec_data_role role);
+void typec_set_pwr_role(struct typec_port *port, enum typec_role role);
+void typec_set_vconn_role(struct typec_port *port, enum typec_role role);
+void typec_set_pwr_opmode(struct typec_port *port, enum typec_pwr_opmode mode);
+
+#endif /* __LINUX_USB_TYPEC_H */
-- 
2.10.2

^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCHv13 3/3] usb: typec: add driver for Intel Whiskey Cove PMIC USB Type-C PHY
  2016-11-24 12:21 [PATCHv13 0/3] USB Type-C Connector class Heikki Krogerus
  2016-11-24 12:21 ` [PATCHv13 1/3] lib/string: add sysfs_match_string helper Heikki Krogerus
  2016-11-24 12:21 ` [PATCHv13 2/3] usb: USB Type-C connector class Heikki Krogerus
@ 2016-11-24 12:21 ` Heikki Krogerus
  2 siblings, 0 replies; 19+ messages in thread
From: Heikki Krogerus @ 2016-11-24 12:21 UTC (permalink / raw)
  To: Greg KH, Guenter Roeck; +Cc: Oliver Neukum, linux-kernel, linux-usb

This adds driver for the USB Type-C PHY on Intel WhiskeyCove
PMIC which is available on some of the Intel Broxton SoC
based platforms.

Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
---
 drivers/usb/typec/Kconfig       |  14 ++
 drivers/usb/typec/Makefile      |   1 +
 drivers/usb/typec/typec_wcove.c | 373 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 388 insertions(+)
 create mode 100644 drivers/usb/typec/typec_wcove.c

diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
index 17792f9..2abbcb0 100644
--- a/drivers/usb/typec/Kconfig
+++ b/drivers/usb/typec/Kconfig
@@ -4,4 +4,18 @@ menu "USB Power Delivery and Type-C drivers"
 config TYPEC
 	tristate
 
+config TYPEC_WCOVE
+	tristate "Intel WhiskeyCove PMIC USB Type-C PHY driver"
+	depends on ACPI
+	depends on INTEL_SOC_PMIC
+	depends on INTEL_PMC_IPC
+	select TYPEC
+	help
+	  This driver adds support for USB Type-C detection on Intel Broxton
+	  platforms that have Intel Whiskey Cove PMIC. The driver can detect the
+	  role and cable orientation.
+
+	  To compile this driver as module, choose M here: the module will be
+	  called typec_wcove
+
 endmenu
diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
index 1012a8b..b9cb862 100644
--- a/drivers/usb/typec/Makefile
+++ b/drivers/usb/typec/Makefile
@@ -1 +1,2 @@
 obj-$(CONFIG_TYPEC)		+= typec.o
+obj-$(CONFIG_TYPEC_WCOVE)	+= typec_wcove.o
diff --git a/drivers/usb/typec/typec_wcove.c b/drivers/usb/typec/typec_wcove.c
new file mode 100644
index 0000000..9065d7b
--- /dev/null
+++ b/drivers/usb/typec/typec_wcove.c
@@ -0,0 +1,373 @@
+/**
+ * typec_wcove.c - WhiskeyCove PMIC USB Type-C PHY driver
+ *
+ * Copyright (C) 2016 Intel Corporation
+ * Author: Heikki Krogerus <heikki.krogerus@linux.intel.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 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/acpi.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/usb/typec.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/intel_soc_pmic.h>
+
+/* Register offsets */
+#define WCOVE_CHGRIRQ0		0x4e09
+#define WCOVE_PHYCTRL		0x5e07
+
+#define USBC_CONTROL1		0x7001
+#define USBC_CONTROL2		0x7002
+#define USBC_CONTROL3		0x7003
+#define USBC_CC1_CTRL		0x7004
+#define USBC_CC2_CTRL		0x7005
+#define USBC_STATUS1		0x7007
+#define USBC_STATUS2		0x7008
+#define USBC_STATUS3		0x7009
+#define USBC_IRQ1		0x7015
+#define USBC_IRQ2		0x7016
+#define USBC_IRQMASK1		0x7017
+#define USBC_IRQMASK2		0x7018
+
+/* Register bits */
+
+#define USBC_CONTROL1_MODE_DRP(r)	(((r) & ~0x7) | 4)
+
+#define USBC_CONTROL2_UNATT_SNK		BIT(0)
+#define USBC_CONTROL2_UNATT_SRC		BIT(1)
+#define USBC_CONTROL2_DIS_ST		BIT(2)
+
+#define USBC_CONTROL3_PD_DIS		BIT(1)
+
+#define USBC_CC_CTRL_VCONN_EN		BIT(1)
+
+#define USBC_STATUS1_DET_ONGOING	BIT(6)
+#define USBC_STATUS1_RSLT(r)		((r) & 0xf)
+#define USBC_RSLT_NOTHING		0
+#define USBC_RSLT_SRC_DEFAULT		1
+#define USBC_RSLT_SRC_1_5A		2
+#define USBC_RSLT_SRC_3_0A		3
+#define USBC_RSLT_SNK			4
+#define USBC_RSLT_DEBUG_ACC		5
+#define USBC_RSLT_AUDIO_ACC		6
+#define USBC_RSLT_UNDEF			15
+#define USBC_STATUS1_ORIENT(r)		(((r) >> 4) & 0x3)
+#define USBC_ORIENT_NORMAL		1
+#define USBC_ORIENT_REVERSE		2
+
+#define USBC_STATUS2_VBUS_REQ		BIT(5)
+
+#define USBC_IRQ1_ADCDONE1		BIT(2)
+#define USBC_IRQ1_OVERTEMP		BIT(1)
+#define USBC_IRQ1_SHORT			BIT(0)
+
+#define USBC_IRQ2_CC_CHANGE		BIT(7)
+#define USBC_IRQ2_RX_PD			BIT(6)
+#define USBC_IRQ2_RX_HR			BIT(5)
+#define USBC_IRQ2_RX_CR			BIT(4)
+#define USBC_IRQ2_TX_SUCCESS		BIT(3)
+#define USBC_IRQ2_TX_FAIL		BIT(2)
+
+#define USBC_IRQMASK1_ALL	(USBC_IRQ1_ADCDONE1 | USBC_IRQ1_OVERTEMP | \
+				 USBC_IRQ1_SHORT)
+
+#define USBC_IRQMASK2_ALL	(USBC_IRQ2_CC_CHANGE | USBC_IRQ2_RX_PD | \
+				 USBC_IRQ2_RX_HR | USBC_IRQ2_RX_CR | \
+				 USBC_IRQ2_TX_SUCCESS | USBC_IRQ2_TX_FAIL)
+
+struct wcove_typec {
+	struct mutex lock; /* device lock */
+	struct device *dev;
+	struct regmap *regmap;
+	struct typec_port *port;
+	struct typec_capability cap;
+	struct typec_connection con;
+	struct typec_partner partner;
+};
+
+enum wcove_typec_func {
+	WCOVE_FUNC_DRIVE_VBUS = 1,
+	WCOVE_FUNC_ORIENTATION,
+	WCOVE_FUNC_ROLE,
+	WCOVE_FUNC_DRIVE_VCONN,
+};
+
+enum wcove_typec_orientation {
+	WCOVE_ORIENTATION_NORMAL,
+	WCOVE_ORIENTATION_REVERSE,
+};
+
+enum wcove_typec_role {
+	WCOVE_ROLE_HOST,
+	WCOVE_ROLE_DEVICE,
+};
+
+static uuid_le uuid = UUID_LE(0x482383f0, 0x2876, 0x4e49,
+			      0x86, 0x85, 0xdb, 0x66, 0x21, 0x1a, 0xf0, 0x37);
+
+static int wcove_typec_func(struct wcove_typec *wcove,
+			    enum wcove_typec_func func, int param)
+{
+	union acpi_object *obj;
+	union acpi_object tmp;
+	union acpi_object argv4 = ACPI_INIT_DSM_ARGV4(1, &tmp);
+
+	tmp.type = ACPI_TYPE_INTEGER;
+	tmp.integer.value = param;
+
+	obj = acpi_evaluate_dsm(ACPI_HANDLE(wcove->dev), uuid.b, 1, func,
+				&argv4);
+	if (!obj) {
+		dev_err(wcove->dev, "%s: failed to evaluate _DSM\n", __func__);
+		return -EIO;
+	}
+
+	ACPI_FREE(obj);
+	return 0;
+}
+
+static void wcove_typec_device_mode(struct wcove_typec *wcove)
+{
+	wcove->con.partner = &wcove->partner;
+	wcove->con.pwr_role = TYPEC_SINK;
+	wcove->con.vconn_role = TYPEC_SINK;
+	wcove_typec_func(wcove, WCOVE_FUNC_ROLE, WCOVE_ROLE_DEVICE);
+	typec_connect(wcove->port, &wcove->con);
+}
+
+static irqreturn_t wcove_typec_irq(int irq, void *data)
+{
+	struct wcove_typec *wcove = data;
+	unsigned int cc1_ctrl;
+	unsigned int cc2_ctrl;
+	unsigned int cc_irq1;
+	unsigned int cc_irq2;
+	unsigned int status1;
+	unsigned int status2;
+	int ret;
+
+	mutex_lock(&wcove->lock);
+
+	ret = regmap_read(wcove->regmap, USBC_IRQ1, &cc_irq1);
+	if (ret)
+		goto err;
+
+	ret = regmap_read(wcove->regmap, USBC_IRQ2, &cc_irq2);
+	if (ret)
+		goto err;
+
+	ret = regmap_read(wcove->regmap, USBC_STATUS1, &status1);
+	if (ret)
+		goto err;
+
+	ret = regmap_read(wcove->regmap, USBC_STATUS2, &status2);
+	if (ret)
+		goto err;
+
+	ret = regmap_read(wcove->regmap, USBC_CC1_CTRL, &cc1_ctrl);
+	if (ret)
+		goto err;
+
+	ret = regmap_read(wcove->regmap, USBC_CC2_CTRL, &cc2_ctrl);
+	if (ret)
+		goto err;
+
+	if (cc_irq1) {
+		if (cc_irq1 & USBC_IRQ1_OVERTEMP)
+			dev_err(wcove->dev, "VCONN Switch Over Temperature!\n");
+		if (cc_irq1 & USBC_IRQ1_SHORT)
+			dev_err(wcove->dev, "VCONN Switch Short Circuit!\n");
+		ret = regmap_write(wcove->regmap, USBC_IRQ1, cc_irq1);
+		if (ret)
+			goto err;
+	}
+
+	if (cc_irq2) {
+		ret = regmap_write(wcove->regmap, USBC_IRQ2, cc_irq2);
+		if (ret)
+			goto err;
+		/*
+		 * Ingoring any PD communication interrupts until the PD stack
+		 * is in place
+		 */
+		if (cc_irq2 & ~USBC_IRQ2_CC_CHANGE) {
+			dev_WARN(wcove->dev, "USB PD handling missing\n");
+			goto err;
+		}
+	}
+
+	if (status1 & USBC_STATUS1_DET_ONGOING)
+		goto out;
+
+	if (USBC_STATUS1_RSLT(status1) == USBC_RSLT_NOTHING) {
+		if (wcove->con.partner) {
+			typec_disconnect(wcove->port);
+			memset(&wcove->con, 0, sizeof(wcove->con));
+			memset(&wcove->partner, 0, sizeof(wcove->partner));
+		}
+
+		wcove_typec_func(wcove, WCOVE_FUNC_ORIENTATION,
+				 WCOVE_ORIENTATION_NORMAL);
+		/* Host mode by default */
+		wcove_typec_func(wcove, WCOVE_FUNC_ROLE, WCOVE_ROLE_HOST);
+		goto out;
+	}
+
+	if (wcove->con.partner)
+		goto out;
+
+	switch (USBC_STATUS1_ORIENT(status1)) {
+	case USBC_ORIENT_NORMAL:
+		wcove_typec_func(wcove, WCOVE_FUNC_ORIENTATION,
+				 WCOVE_ORIENTATION_NORMAL);
+		break;
+	case USBC_ORIENT_REVERSE:
+		wcove_typec_func(wcove, WCOVE_FUNC_ORIENTATION,
+				 WCOVE_ORIENTATION_REVERSE);
+	default:
+		break;
+	}
+
+	switch (USBC_STATUS1_RSLT(status1)) {
+	case USBC_RSLT_SRC_DEFAULT:
+		wcove->con.pwr_opmode = TYPEC_PWR_MODE_USB;
+		wcove_typec_device_mode(wcove);
+		break;
+	case USBC_RSLT_SRC_1_5A:
+		wcove->con.pwr_opmode = TYPEC_PWR_MODE_1_5A;
+		wcove_typec_device_mode(wcove);
+		break;
+	case USBC_RSLT_SRC_3_0A:
+		wcove->con.pwr_opmode = TYPEC_PWR_MODE_3_0A;
+		wcove_typec_device_mode(wcove);
+		break;
+	case USBC_RSLT_SNK:
+		wcove->con.partner = &wcove->partner;
+		wcove->con.data_role = TYPEC_HOST;
+		wcove->con.pwr_role = TYPEC_SOURCE;
+		wcove->con.vconn_role = TYPEC_SOURCE;
+		wcove_typec_func(wcove, WCOVE_FUNC_ROLE, WCOVE_ROLE_HOST);
+		typec_connect(wcove->port, &wcove->con);
+		break;
+	case USBC_RSLT_DEBUG_ACC:
+		wcove->partner.accessory = TYPEC_ACCESSORY_DEBUG;
+		wcove->con.partner = &wcove->partner;
+		typec_connect(wcove->port, &wcove->con);
+		break;
+	case USBC_RSLT_AUDIO_ACC:
+		wcove->partner.accessory = TYPEC_ACCESSORY_AUDIO;
+		wcove->con.partner = &wcove->partner;
+		typec_connect(wcove->port, &wcove->con);
+		break;
+	default:
+		dev_WARN(wcove->dev, "%s Undefined result\n", __func__);
+		goto err;
+	}
+out:
+	/* If either CC pins is requesting VCONN, we turn it on */
+	if ((cc1_ctrl & USBC_CC_CTRL_VCONN_EN) ||
+	    (cc2_ctrl &	USBC_CC_CTRL_VCONN_EN))
+		wcove_typec_func(wcove, WCOVE_FUNC_DRIVE_VCONN, true);
+	else
+		wcove_typec_func(wcove, WCOVE_FUNC_DRIVE_VCONN, false);
+
+	/* Relying on the FSM to know when we need to drive VBUS. */
+	wcove_typec_func(wcove, WCOVE_FUNC_DRIVE_VBUS,
+			 !!(status2 & USBC_STATUS2_VBUS_REQ));
+err:
+	/* REVISIT: Clear WhiskeyCove CHGR Type-C interrupt */
+	regmap_write(wcove->regmap, WCOVE_CHGRIRQ0, BIT(5));
+
+	mutex_unlock(&wcove->lock);
+	return IRQ_HANDLED;
+}
+
+static int wcove_typec_probe(struct platform_device *pdev)
+{
+	struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent);
+	struct wcove_typec *wcove;
+	unsigned int val;
+	int ret;
+
+	wcove = devm_kzalloc(&pdev->dev, sizeof(*wcove), GFP_KERNEL);
+	if (!wcove)
+		return -ENOMEM;
+
+	mutex_init(&wcove->lock);
+	wcove->dev = &pdev->dev;
+	wcove->regmap = pmic->regmap;
+
+	ret = regmap_irq_get_virq(pmic->irq_chip_data_level2,
+				  platform_get_irq(pdev, 0));
+	if (ret < 0)
+		return ret;
+
+	ret = devm_request_threaded_irq(&pdev->dev, ret, NULL,
+					wcove_typec_irq, IRQF_ONESHOT,
+					"wcove_typec", wcove);
+	if (ret)
+		return ret;
+
+	if (!acpi_check_dsm(ACPI_HANDLE(&pdev->dev), uuid.b, 0, 0x1f)) {
+		dev_err(&pdev->dev, "Missing _DSM functions\n");
+		return -ENODEV;
+	}
+
+	wcove->cap.type = TYPEC_PORT_DRP;
+	wcove->cap.prefer_role = TYPEC_NO_PREFERRED_ROLE;
+
+	/* Make sure the PD PHY is disabled until PD stack is ready */
+	regmap_read(wcove->regmap, USBC_CONTROL3, &val);
+	regmap_write(wcove->regmap, USBC_CONTROL3, val | USBC_CONTROL3_PD_DIS);
+
+	/* DRP mode without accessory support */
+	regmap_read(wcove->regmap, USBC_CONTROL1, &val);
+	regmap_write(wcove->regmap, USBC_CONTROL1, USBC_CONTROL1_MODE_DRP(val));
+
+	wcove->port = typec_register_port(&pdev->dev, &wcove->cap);
+	if (IS_ERR(wcove->port))
+		return PTR_ERR(wcove->port);
+
+	/* Unmask everything */
+	regmap_read(wcove->regmap, USBC_IRQMASK1, &val);
+	regmap_write(wcove->regmap, USBC_IRQMASK1, val & ~USBC_IRQMASK1_ALL);
+	regmap_read(wcove->regmap, USBC_IRQMASK2, &val);
+	regmap_write(wcove->regmap, USBC_IRQMASK2, val & ~USBC_IRQMASK2_ALL);
+
+	platform_set_drvdata(pdev, wcove);
+	return 0;
+}
+
+static int wcove_typec_remove(struct platform_device *pdev)
+{
+	struct wcove_typec *wcove = platform_get_drvdata(pdev);
+	unsigned int val;
+
+	/* Mask everything */
+	regmap_read(wcove->regmap, USBC_IRQMASK1, &val);
+	regmap_write(wcove->regmap, USBC_IRQMASK1, val | USBC_IRQMASK1_ALL);
+	regmap_read(wcove->regmap, USBC_IRQMASK2, &val);
+	regmap_write(wcove->regmap, USBC_IRQMASK2, val | USBC_IRQMASK2_ALL);
+
+	typec_unregister_port(wcove->port);
+	return 0;
+}
+
+static struct platform_driver wcove_typec_driver = {
+	.driver = {
+		.name		= "bxt_wcove_usbc",
+	},
+	.probe			= wcove_typec_probe,
+	.remove			= wcove_typec_remove,
+};
+
+module_platform_driver(wcove_typec_driver);
+
+MODULE_AUTHOR("Intel Corporation");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("WhiskeyCove PMIC USB Type-C PHY driver");
+MODULE_ALIAS("platform:bxt_wcove_usbc");
-- 
2.10.2

^ permalink raw reply related	[flat|nested] 19+ messages in thread

* Re: [PATCHv13 2/3] usb: USB Type-C connector class
  2016-11-24 12:21 ` [PATCHv13 2/3] usb: USB Type-C connector class Heikki Krogerus
@ 2016-11-29 16:27   ` Greg KH
  2016-11-30  9:19     ` Heikki Krogerus
  0 siblings, 1 reply; 19+ messages in thread
From: Greg KH @ 2016-11-29 16:27 UTC (permalink / raw)
  To: Heikki Krogerus; +Cc: Guenter Roeck, Oliver Neukum, linux-kernel, linux-usb

On Thu, Nov 24, 2016 at 02:21:43PM +0200, Heikki Krogerus wrote:
> The purpose of USB Type-C connector class is to provide
> unified interface for the user space to get the status and
> basic information about USB Type-C connectors on a system,
> control over data role swapping, and when the port supports
> USB Power Delivery, also control over power role swapping
> and Alternate Modes.

Ok, something is really "odd" about how you are abusing the driver model
here, just by how the release functions are written.  So, let me try to
figure it out...

I'm cutting and pasting all over the place, hopefully it makes some
sense:


> --- /dev/null
> +++ b/Documentation/usb/typec.txt
> @@ -0,0 +1,110 @@
> +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 the user space control
> +over the roles and alternate modes they support 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.
> +
> +
> +Interface
> +---------
> +Every port will be presented as its own device under /sys/class/typec/. The
> +first port will be named "usbc0", the second "usbc1" 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. The
> +partner attached to port "usbc0" will be named "usbc0-partner". Full path to the
> +device would be /sys/class/typec/usb0/usb0-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 "usbc0" port
> +will be named usbc0-cable and the plug on the SOP Prime end (see USB Power
> +Delivery Specification ch. 2.4) will be named "usbc-plug0" and on the SOP Double
> +Prime end "usbc0-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 support Alternate Modes, every Alternate Mode
> +SVID will have their own device describing them. The Alternate Modes will not be
> +attached to the typec class. For the port's "usbc0" partner, the Alternate Modes
> +would have devices presented under /sys/class/typec/usbc0-partner/. Every mode
> +that is supported will have its own group under the Alternate Mode device named
> +"mode<id>". For example /sys/class/typec/usbc0/usbc0.svid:xxxx/mode0/. The
> +requests for entering/exiting the modes happens with the "active" attribute in
> +that group.
> +
> +
> +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:
> +
> +struct typec_port *typec_register_port(struct device *dev,
> +				       const struct typec_capability *cap);

'dev' should be 'parent' to make things a bit more obvious.  Ok, that
looks good, and the code there seems semi-sane:

> +struct typec_port *typec_register_port(struct device *dev,
> +				       const struct typec_capability *cap)
> +{
> +	struct typec_port *port;
> +	int ret;
> +	int id;
> +
> +	port = kzalloc(sizeof(*port), GFP_KERNEL);
> +	if (!port)
> +		return ERR_PTR(-ENOMEM);

Minor nit, who really cares?  Just return NULL and get on with it :)

> +
> +	id = ida_simple_get(&typec_index_ida, 0, 0, GFP_KERNEL);
> +	if (id < 0) {
> +		kfree(port);
> +		return ERR_PTR(id);
> +	}
> +
> +	port->prefer_role = cap->prefer_role;
> +
> +	port->id = id;
> +	port->cap = cap;
> +	port->dev.type = &typec_port_dev_type;
> +	port->dev.class = &typec_class;
> +	port->dev.parent = dev;
> +	dev_set_name(&port->dev, "usbc%d", id);
> +
> +	typec_init_roles(port);
> +
> +	ret = device_register(&port->dev);
> +	if (ret)
> +		goto reg_err;

Hm, you just announced this device to the world, which is fine, but do
you really want to do that?  Are all attributes properly set up and
ready to be read?  I think this is true, and your release function for
this type of device is great, as it actually frees the memory involved,
so good, nice work.

So let's move on, to what caused me to go "hmmm..."

> +* Notifications
> +
> +When connection happens on a port, the port driver fills struct typec_connection
> +which is passed to the class. The class provides the following API for reporting
> +connection/disconnection:

typec_connection is "odd":

> +/*
> + * struct typec_connection - Details about USB Type-C port connection event
> + * @partner: The attached partner
> + * @cable: The attached cable
> + * @data_role: Initial USB data role (host or device)
> + * @pwr_role: Initial Power role (source or sink)
> + * @vconn_role: Initial VCONN role (source or sink)
> + * @pwr_opmode: The power mode of the connection
> + *
> + * All the relevant details about a connection event. Wrapper that is passed to
> + * typec_connect(). The context is copied when typec_connect() is called and the
> + * structure is not used for anything else.
> + */
> +struct typec_connection {
> +	struct typec_partner	*partner;
> +	struct typec_cable	*cable;
> +
> +	enum typec_data_role	data_role;
> +	enum typec_role		pwr_role;
> +	enum typec_role		vconn_role;
> +	enum typec_pwr_opmode	pwr_opmode;
> +};
> +

You have a partner, and a cable structure pointer.  Yet, who created
those structures?  I see who registers them, but who "owns" them?

Obviously, not this code, because of this release call for the
"partner":

> +static void typec_partner_release(struct device *dev)
> +{
> +	struct typec_port *port = to_typec_port(dev->parent);
> +
> +	port->partner = NULL;
> +}

What did you do here?  You just ignored the struct device, didn't free
anything, and am just "hoping" someone else will do it.

Ick ick ick.

Then your typec_cable release function:

> +static void typec_plug_release(struct device *dev)
> +{
> +	struct typec_plug *plug = to_typec_plug(dev);
> +
> +	memset(plug, 0, sizeof(*plug));
> +}

Um, no.  NEVER zero out a struct device, you just re-initialized it in a
back-door-way-guaranteed-to-cause-oopses-and-memory-loss.  It's worse
than just ignoring it.

So, what is the lifecycle for these devices?  Who "owns" them?  Why
doesn't the core own them?  It is responsible for registering and doing
stuff with them, yet it doesn't create or free them?  That's a reciepie
for disaster.

I guess I should have been harsher when I saw your empty release
functions and just assumed you would fix this up properly.  This code is
totally wrong and needs to be reworked to correctly manage the lifecycle
of the struct devices being used here.  Don't paper over kernel warnings
by providing empty release functions, and then, when I complain about
them, provide code that is even worse!

You are getting lucky that this code seems to work, but it really
doesn't.  Go read the section in CodingStyle about reference counting of
objects.  The driver model does this for you, but you can't just ignore
reality and go "hopefully someone else will free the memory when we are
done", like you are doing here.  Actually it's worse as the device here
is now gone, yet you are lying and saying "hey driver core, someone else
is going to free it up because it 'knows' it's now released, because we
lied about who owns this device, it really wasn't you".


Ok, I just tried to look at the code more, and got even more worried...

Look at this mess:

> +/*
> + * struct typec_plug - USB Type-C Cable Plug
> + * @dev: struct device instance
> + * @index: 1 for the plug connected to DFP and 2 for the plug connected to UFP
> + * @alt_modes: Alternate Modes the cable plug supports (null terminated)
> + *
> + * Represents USB Type-C Cable Plug.
> + */
> +struct typec_plug {
> +	struct device		dev;
> +	int			index;
> +	struct typec_altmode	*alt_modes;
> +};

Ok, a plug is a struct device, nice...

> +
> +/*
> + * struct typec_cable - USB Type-C Cable
> + * @dev: struct device instance
> + * @type: The plug type from USB PD Cable VDO
> + * @usb_pd: Electronically Marked Cable
> + * @active: Is the cable active or passive
> + * @sop_pp_controller: Tells whether both cable plugs are configurable or not
> + * @plug: The two plugs in the cable.
> + *
> + * Represents USB Type-C Cable attached to USB Type-C port. Two plugs are
> + * created if the cable has SOP Double Prime controller as defined in USB PD
> + * specification. Otherwise only one will be created if the cable is active. For
> + * passive cables no plugs are created.
> + */
> +struct typec_cable {
> +	struct device		dev;
> +	enum typec_plug_type	type;
> +	u32			vdo;
> +	unsigned int		usb_pd:1;
> +	unsigned int		active:1;
> +	unsigned int		sop_pp_controller:1;
> +
> +	struct typec_plug	plug[2];

WTF???

Think about what this structure now represents.  You have 3 different
reference counted objects, all embedded in the same structure.  Who
"owns" the lifecycle of it?  What happens if plug[1]'s reference count
is grabbed a bunch by someone reading a lot of files for it, and then
the "larger" typec_cable.dev reference count is decremented to 0 because
the core is done with it.  oops, boom, ick, splat, and if you are lucky
the device reboots itself, if not, someone just got root and read your
bank account information...

{sigh}

I'm being harsh here because this is really really really badly designed
code.  Go back and think about your data structures, what they are
trying to represent, and _WHO_ owns and controls them.  The typec core
should be the one that allocates and manages the lifecycle of them, not
some random external entity where you just hope and pray that they got
it right (hint, they can not as they do not know what the core did with
the reference counts.)

Right now you are almost there, the typec core registers and tries to
manage the structures, but it doesn't allocate/free them, and that's the
big problem, because really, with a structure that has 3 different
reference counts, no one can.  That's an impossibility.

This needs a lot more work, sorry.

I'm now going to require that you get other internal Intel developers to
sign off on this code before I review it again.  You have resources at
your disposal that others do not with your internal mailing lists
containing senior kernel developers.  Use it and don't waste the
community's time to do basic code review that they should be doing
instead.

How did we get to a v13 of this patch series without anyone else even
seeing this?  That's worrysome as well...

greg k-h

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCHv13 2/3] usb: USB Type-C connector class
  2016-11-29 16:27   ` Greg KH
@ 2016-11-30  9:19     ` Heikki Krogerus
  2016-12-02 18:04       ` Guenter Roeck
  0 siblings, 1 reply; 19+ messages in thread
From: Heikki Krogerus @ 2016-11-30  9:19 UTC (permalink / raw)
  To: Greg KH; +Cc: Guenter Roeck, Oliver Neukum, linux-kernel, linux-usb

Hi Greg,

On Tue, Nov 29, 2016 at 05:27:44PM +0100, Greg KH wrote:
> > +struct typec_cable {
> > +	struct device		dev;
> > +	enum typec_plug_type	type;
> > +	u32			vdo;
> > +	unsigned int		usb_pd:1;
> > +	unsigned int		active:1;
> > +	unsigned int		sop_pp_controller:1;
> > +
> > +	struct typec_plug	plug[2];
> 
> WTF???
> 
> Think about what this structure now represents.  You have 3 different
> reference counted objects, all embedded in the same structure.  Who
> "owns" the lifecycle of it?  What happens if plug[1]'s reference count
> is grabbed a bunch by someone reading a lot of files for it, and then
> the "larger" typec_cable.dev reference count is decremented to 0 because
> the core is done with it.  oops, boom, ick, splat, and if you are lucky
> the device reboots itself, if not, someone just got root and read your
> bank account information...

I have to ask. How could that happen since the cable is the parent?

> I'm being harsh here because this is really really really badly designed

Don't worry about it, I can handle it. In fact, one could argue that I
like getting slapped by you based on the comments I keep getting, but
I assure you that is not the case ;-)

> code.  Go back and think about your data structures, what they are
> trying to represent, and _WHO_ owns and controls them.  The typec core
> should be the one that allocates and manages the lifecycle of them, not
> some random external entity where you just hope and pray that they got
> it right (hint, they can not as they do not know what the core did with
> the reference counts.)
> 
> Right now you are almost there, the typec core registers and tries to
> manage the structures, but it doesn't allocate/free them, and that's the
> big problem, because really, with a structure that has 3 different
> reference counts, no one can.  That's an impossibility.
> 
> This needs a lot more work, sorry.

I was trying to cut corners, which clearly was wrong. I know what I
need to do. All the parts simply need to be registered normally. No
shortcuts.

> I'm now going to require that you get other internal Intel developers to
> sign off on this code before I review it again.  You have resources at
> your disposal that others do not with your internal mailing lists
> containing senior kernel developers.  Use it and don't waste the
> community's time to do basic code review that they should be doing
> instead.

Fair enough.

> How did we get to a v13 of this patch series without anyone else even
> seeing this?  That's worrysome as well...

I guess for somebody writing the port drivers my awesome shortcut felt
kinda nice. All they would have to do is announce connection when it
happens, and the class then tried figured out everything for them,
what needs to be created and so on. But yes, that is wrong!

But man, v14! I have got to be breaking the record with this one.


Thanks,

-- 
heikki

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCHv13 2/3] usb: USB Type-C connector class
  2016-11-30  9:19     ` Heikki Krogerus
@ 2016-12-02 18:04       ` Guenter Roeck
  2016-12-02 18:22         ` Greg KH
                           ` (2 more replies)
  0 siblings, 3 replies; 19+ messages in thread
From: Guenter Roeck @ 2016-12-02 18:04 UTC (permalink / raw)
  To: Heikki Krogerus; +Cc: Greg KH, Oliver Neukum, linux-kernel, linux-usb

On Wed, Nov 30, 2016 at 11:19:10AM +0200, Heikki Krogerus wrote:
> Hi Greg,
> 
> On Tue, Nov 29, 2016 at 05:27:44PM +0100, Greg KH wrote:
> > > +struct typec_cable {
> > > +	struct device		dev;
> > > +	enum typec_plug_type	type;
> > > +	u32			vdo;
> > > +	unsigned int		usb_pd:1;
> > > +	unsigned int		active:1;
> > > +	unsigned int		sop_pp_controller:1;
> > > +
> > > +	struct typec_plug	plug[2];
> > 
> > WTF???
> > 
> > Think about what this structure now represents.  You have 3 different
> > reference counted objects, all embedded in the same structure.  Who
> > "owns" the lifecycle of it?  What happens if plug[1]'s reference count
> > is grabbed a bunch by someone reading a lot of files for it, and then
> > the "larger" typec_cable.dev reference count is decremented to 0 because
> > the core is done with it.  oops, boom, ick, splat, and if you are lucky
> > the device reboots itself, if not, someone just got root and read your
> > bank account information...
> 
> I have to ask. How could that happen since the cable is the parent?
> 
> > I'm being harsh here because this is really really really badly designed
> 
> Don't worry about it, I can handle it. In fact, one could argue that I
> like getting slapped by you based on the comments I keep getting, but
> I assure you that is not the case ;-)
> 
> > code.  Go back and think about your data structures, what they are
> > trying to represent, and _WHO_ owns and controls them.  The typec core
> > should be the one that allocates and manages the lifecycle of them, not
> > some random external entity where you just hope and pray that they got
> > it right (hint, they can not as they do not know what the core did with
> > the reference counts.)
> > 
> > Right now you are almost there, the typec core registers and tries to
> > manage the structures, but it doesn't allocate/free them, and that's the
> > big problem, because really, with a structure that has 3 different
> > reference counts, no one can.  That's an impossibility.
> > 
> > This needs a lot more work, sorry.
> 
> I was trying to cut corners, which clearly was wrong. I know what I
> need to do. All the parts simply need to be registered normally. No
> shortcuts.
> 
> > I'm now going to require that you get other internal Intel developers to
> > sign off on this code before I review it again.  You have resources at
> > your disposal that others do not with your internal mailing lists
> > containing senior kernel developers.  Use it and don't waste the
> > community's time to do basic code review that they should be doing
> > instead.
> 
> Fair enough.
> 
> > How did we get to a v13 of this patch series without anyone else even
> > seeing this?  That's worrysome as well...
> 
> I guess for somebody writing the port drivers my awesome shortcut felt
> kinda nice. All they would have to do is announce connection when it
> happens, and the class then tried figured out everything for them,
> what needs to be created and so on. But yes, that is wrong!
> 
At least for my part I very much concentrated on making sure that
the user space ABI as well as the port driver API are sane and usable.

The driver interface is not my area of expertise. As such, my testing
and understanding of that part was limited to "it appears to work,
it must be ok". I very much relied on you to get this part right.

That makes me feel really bad. It isn't fun to have my "Reviewed-by"
on a patch that gets (and apparently deserves) a WTF from a senior
kernel maintainer. This hurts both your and my reputation, and obviously
will make me quite hesitant to add a "Reviewed-by:" to the next version
of the series.

No more shortcuts, please.

Thanks,
Guenter

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCHv13 2/3] usb: USB Type-C connector class
  2016-12-02 18:04       ` Guenter Roeck
@ 2016-12-02 18:22         ` Greg KH
  2016-12-07  9:11         ` Heikki Krogerus
  2016-12-07  9:46         ` Oliver Neukum
  2 siblings, 0 replies; 19+ messages in thread
From: Greg KH @ 2016-12-02 18:22 UTC (permalink / raw)
  To: Guenter Roeck; +Cc: Heikki Krogerus, Oliver Neukum, linux-kernel, linux-usb

On Fri, Dec 02, 2016 at 10:04:39AM -0800, Guenter Roeck wrote:
> The driver interface is not my area of expertise. As such, my testing
> and understanding of that part was limited to "it appears to work,
> it must be ok". I very much relied on you to get this part right.
> 
> That makes me feel really bad. It isn't fun to have my "Reviewed-by"
> on a patch that gets (and apparently deserves) a WTF from a senior
> kernel maintainer. This hurts both your and my reputation, and obviously
> will make me quite hesitant to add a "Reviewed-by:" to the next version
> of the series.

Hey, it doesn't bother me at all, I want and need your reviews of those
portions that I don't know as well (i.e. the userspace api and
functionality.)   So don't take it personally, the driver model isn't
that easy of a topic to mess with in places, loads of people get it
wrong.

> No more shortcuts, please.

I totally agree :)

Also, I want some Intel reviews on this, that should help make a lot of
these types of issues go away...

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCHv13 2/3] usb: USB Type-C connector class
  2016-12-02 18:04       ` Guenter Roeck
  2016-12-02 18:22         ` Greg KH
@ 2016-12-07  9:11         ` Heikki Krogerus
  2016-12-07  9:46         ` Oliver Neukum
  2 siblings, 0 replies; 19+ messages in thread
From: Heikki Krogerus @ 2016-12-07  9:11 UTC (permalink / raw)
  To: Guenter Roeck; +Cc: Greg KH, Oliver Neukum, linux-kernel, linux-usb

On Fri, Dec 02, 2016 at 10:04:39AM -0800, Guenter Roeck wrote:
> At least for my part I very much concentrated on making sure that
> the user space ABI as well as the port driver API are sane and usable.
> 
> The driver interface is not my area of expertise. As such, my testing
> and understanding of that part was limited to "it appears to work,
> it must be ok". I very much relied on you to get this part right.
> 
> That makes me feel really bad. It isn't fun to have my "Reviewed-by"
> on a patch that gets (and apparently deserves) a WTF from a senior
> kernel maintainer. This hurts both your and my reputation, and obviously
> will make me quite hesitant to add a "Reviewed-by:" to the next version
> of the series.

I don't usually get very upset about stronger comments, even if they
are coming from senior kernel maintainer like Greg, but your concern
does make me feel like crap. I'm sorry Guenter.

Please just note that nobody else has considered the approach as a
problem or noticed it before Greg any more then you, and this code has
by now been review by many, including some people who I consider senior
kernel developers and maintainers, both internally inside Intel and
also publicly on these lists.

> No more shortcuts, please.

No.


Br,

-- 
heikki

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCHv13 2/3] usb: USB Type-C connector class
  2016-12-02 18:04       ` Guenter Roeck
  2016-12-02 18:22         ` Greg KH
  2016-12-07  9:11         ` Heikki Krogerus
@ 2016-12-07  9:46         ` Oliver Neukum
  2016-12-07 12:52           ` Heikki Krogerus
  2 siblings, 1 reply; 19+ messages in thread
From: Oliver Neukum @ 2016-12-07  9:46 UTC (permalink / raw)
  To: Guenter Roeck; +Cc: Heikki Krogerus, Greg KH, linux-kernel, linux-usb

On Fri, 2016-12-02 at 10:04 -0800, Guenter Roeck wrote:

Hi,

> At least for my part I very much concentrated on making sure that
> the user space ABI as well as the port driver API are sane and usable.

Rightly so, as this part cannot be changed once included in a kernel
release.
So, can we agree that that part at least is ready to go?

> The driver interface is not my area of expertise. As such, my testing
> and understanding of that part was limited to "it appears to work,
> it must be ok". I very much relied on you to get this part right.
> 
> That makes me feel really bad. It isn't fun to have my "Reviewed-by"
> on a patch that gets (and apparently deserves) a WTF from a senior
> kernel maintainer. This hurts both your and my reputation, and obviously
> will make me quite hesitant to add a "Reviewed-by:" to the next version
> of the series.

The driver model is arcane. It is the reason we have people who really
understand it review code.

But I think it is a reason we need to question assumptions.
Is it really true that the lifetimes of both ends of a plug
are tightly locked? What happens if you unplug a cable
whose ends have different power supplies?

	Regards
		Oliver

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCHv13 2/3] usb: USB Type-C connector class
  2016-12-07  9:46         ` Oliver Neukum
@ 2016-12-07 12:52           ` Heikki Krogerus
  2016-12-15 11:50             ` Heikki Krogerus
  0 siblings, 1 reply; 19+ messages in thread
From: Heikki Krogerus @ 2016-12-07 12:52 UTC (permalink / raw)
  To: Oliver Neukum; +Cc: Guenter Roeck, Greg KH, linux-kernel, linux-usb

Hi Oliver,

On Wed, Dec 07, 2016 at 10:46:48AM +0100, Oliver Neukum wrote:
> On Fri, 2016-12-02 at 10:04 -0800, Guenter Roeck wrote:
> 
> Hi,
> 
> > At least for my part I very much concentrated on making sure that
> > the user space ABI as well as the port driver API are sane and usable.
> 
> Rightly so, as this part cannot be changed once included in a kernel
> release.
> So, can we agree that that part at least is ready to go?

I at least have no plans on doing any changes to the ABI. The API will
change.

> > The driver interface is not my area of expertise. As such, my testing
> > and understanding of that part was limited to "it appears to work,
> > it must be ok". I very much relied on you to get this part right.
> > 
> > That makes me feel really bad. It isn't fun to have my "Reviewed-by"
> > on a patch that gets (and apparently deserves) a WTF from a senior
> > kernel maintainer. This hurts both your and my reputation, and obviously
> > will make me quite hesitant to add a "Reviewed-by:" to the next version
> > of the series.
> 
> The driver model is arcane. It is the reason we have people who really
> understand it review code.
> 
> But I think it is a reason we need to question assumptions.
> Is it really true that the lifetimes of both ends of a plug
> are tightly locked? What happens if you unplug a cable
> whose ends have different power supplies?

This is just a sidenote. Since both plugs will be registered and
unregistered separately with the new API I'm going to propose,
removing only one of the plugs will then be possible. I can't say if
the specifications actually allow that, but the API will not block
it. Both plugs are in any case represented as their own devices with
the cable as the parent as before.


Thanks,

-- 
heikki

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCHv13,1/3] lib/string: add sysfs_match_string helper
  2016-11-24 12:21 ` [PATCHv13 1/3] lib/string: add sysfs_match_string helper Heikki Krogerus
@ 2016-12-07 23:54   ` Guenter Roeck
  2016-12-08  7:29     ` Heikki Krogerus
  0 siblings, 1 reply; 19+ messages in thread
From: Guenter Roeck @ 2016-12-07 23:54 UTC (permalink / raw)
  To: Heikki Krogerus; +Cc: Greg KH, Oliver Neukum, linux-kernel, linux-usb

On Thu, Nov 24, 2016 at 02:21:42PM +0200, Heikki Krogerus wrote:
> Make a simple helper for matching strings with sysfs
> attribute files. In most parts the same as match_string(),
> except sysfs_match_string() uses sysfs_streq() instead of
> strcmp() for matching. This is more convenient when used
> with sysfs attributes.
> 
> Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> Reviewed-by: Guenter Roeck <linux@roeck-us.net>
> Tested-by: Guenter Roeck <linux@roeck-us.net>
> ---
>  include/linux/string.h | 10 ++++++++++
>  lib/string.c           | 26 ++++++++++++++++++++++++++
>  2 files changed, 36 insertions(+)
> 
> diff --git a/include/linux/string.h b/include/linux/string.h
> index 26b6f6a..c4011b2 100644
> --- a/include/linux/string.h
> +++ b/include/linux/string.h
> @@ -135,6 +135,16 @@ static inline int strtobool(const char *s, bool *res)
>  }
>  
>  int match_string(const char * const *array, size_t n, const char *string);
> +int __sysfs_match_string(const char * const *array, size_t n, const char *s);
> +
> +/**
> + * sysfs_match_string - matches given string in an array
> + * @_a: array of strings
> + * @_s: string to match with
> + *
> + * Helper for __sysfs_match_string(). Calculates the size of @a automatically.
> + */
> +#define sysfs_match_string(_a, _s) __sysfs_match_string(_a, ARRAY_SIZE(_a), _s)
>  
>  #ifdef CONFIG_BINARY_PRINTF
>  int vbin_printf(u32 *bin_buf, size_t size, const char *fmt, va_list args);
> diff --git a/lib/string.c b/lib/string.c
> index ed83562..c7a20cb 100644
> --- a/lib/string.c
> +++ b/lib/string.c
> @@ -656,6 +656,32 @@ int match_string(const char * const *array, size_t n, const char *string)
>  }
>  EXPORT_SYMBOL(match_string);
>  
> +/**
> + * __sysfs_match_string - matches given string in an array
> + * @array: array of strings
> + * @n: number of strings in the array or -1 for NULL terminated arrays
> + * @str: string to match with
> + *
> + * Returns index of @str in the @array or -EINVAL, just like match_string().
> + * Uses sysfs_streq instead of strcmp for matching.
> + */
> +int __sysfs_match_string(const char * const *array, size_t n, const char *str)
> +{
> +	const char *item;
> +	int index;
> +
> +	for (index = 0; index < n; index++) {
> +		item = array[index];
> +		if (!item)
> +			break;
> +		if (!sysfs_streq(item, str))

No idea how this slipped in: sysfs_streq() returns true on a match,
meaning the "!" here is wrong.

Guenter

> +			return index;
> +	}
> +
> +	return -EINVAL;
> +}
> +EXPORT_SYMBOL(__sysfs_match_string);
> +
>  #ifndef __HAVE_ARCH_MEMSET
>  /**
>   * memset - Fill a region of memory with the given value

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCHv13,1/3] lib/string: add sysfs_match_string helper
  2016-12-07 23:54   ` [PATCHv13,1/3] " Guenter Roeck
@ 2016-12-08  7:29     ` Heikki Krogerus
  0 siblings, 0 replies; 19+ messages in thread
From: Heikki Krogerus @ 2016-12-08  7:29 UTC (permalink / raw)
  To: Guenter Roeck; +Cc: Greg KH, Oliver Neukum, linux-kernel, linux-usb

On Wed, Dec 07, 2016 at 03:54:45PM -0800, Guenter Roeck wrote:
> > +int __sysfs_match_string(const char * const *array, size_t n, const char *str)
> > +{
> > +	const char *item;
> > +	int index;
> > +
> > +	for (index = 0; index < n; index++) {
> > +		item = array[index];
> > +		if (!item)
> > +			break;
> > +		if (!sysfs_streq(item, str))
> 
> No idea how this slipped in: sysfs_streq() returns true on a match,
> meaning the "!" here is wrong.

Good catch. Thanks Guenter.

-- 
heikki

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCHv13 2/3] usb: USB Type-C connector class
  2016-12-07 12:52           ` Heikki Krogerus
@ 2016-12-15 11:50             ` Heikki Krogerus
  2016-12-15 12:06               ` Oliver Neukum
  2016-12-15 15:01               ` Guenter Roeck
  0 siblings, 2 replies; 19+ messages in thread
From: Heikki Krogerus @ 2016-12-15 11:50 UTC (permalink / raw)
  To: Oliver Neukum; +Cc: Guenter Roeck, Greg KH, linux-kernel, linux-usb

On Wed, Dec 07, 2016 at 02:52:31PM +0200, Heikki Krogerus wrote:
> Hi Oliver,
> 
> On Wed, Dec 07, 2016 at 10:46:48AM +0100, Oliver Neukum wrote:
> > On Fri, 2016-12-02 at 10:04 -0800, Guenter Roeck wrote:
> > 
> > Hi,
> > 
> > > At least for my part I very much concentrated on making sure that
> > > the user space ABI as well as the port driver API are sane and usable.
> > 
> > Rightly so, as this part cannot be changed once included in a kernel
> > release.
> > So, can we agree that that part at least is ready to go?
> 
> I at least have no plans on doing any changes to the ABI. The API will
> change.

I have received (off-list) some questions related to this. There are a
few things that people would like to still change after all.

1) Should we use "source" and "sink" instead of "device" and "host"
with the prefer_role attribute after all? I was uncomfortable with
that when we talked about it last time because the terms kept changing
with every new Type-C specification version. But I guess "source" and
"sink" would make sense in the end. This has been requested by several
guys now.

2) Can we change the way we list the supported roles? They are now
comma separated, but can we use new line instead:

        % cat supported_data_roles
        host
        device
        %

3) Instead of the "supports_usb_power_delivery" attribute file which
we have for the ports and partners, it seem it would be more
interesting to know the supported USB Power Delivery Specification
version for some, and also USB Type-C Specification version in
separate attribute file. So basically we would have
"usb_power_delivery_version" and "usb_typec_version" attributes
instead or "supports_usb_power_delivery".


So would these changes be OK still at this point to everybody?


Thanks,

-- 
heikki

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCHv13 2/3] usb: USB Type-C connector class
  2016-12-15 11:50             ` Heikki Krogerus
@ 2016-12-15 12:06               ` Oliver Neukum
  2016-12-15 15:01               ` Guenter Roeck
  1 sibling, 0 replies; 19+ messages in thread
From: Oliver Neukum @ 2016-12-15 12:06 UTC (permalink / raw)
  To: Heikki Krogerus; +Cc: Greg KH, Guenter Roeck, linux-kernel, linux-usb

On Thu, 2016-12-15 at 13:50 +0200, Heikki Krogerus wrote:

> 1) Should we use "source" and "sink" instead of "device" and "host"
> with the prefer_role attribute after all? I was uncomfortable with
> that when we talked about it last time because the terms kept changing
> with every new Type-C specification version. But I guess "source" and
> "sink" would make sense in the end. This has been requested by several
> guys now.
> 
> 2) Can we change the way we list the supported roles? They are now
> comma separated, but can we use new line instead:
> 
>         % cat supported_data_roles
>         host
>         device
>         %
> 
> 3) Instead of the "supports_usb_power_delivery" attribute file which
> we have for the ports and partners, it seem it would be more
> interesting to know the supported USB Power Delivery Specification
> version for some, and also USB Type-C Specification version in
> separate attribute file. So basically we would have
> "usb_power_delivery_version" and "usb_typec_version" attributes
> instead or "supports_usb_power_delivery".
> 
> 
> So would these changes be OK still at this point to everybody?

Hi,

they are all right with me.

	Regards
		Oliver

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCHv13 2/3] usb: USB Type-C connector class
  2016-12-15 11:50             ` Heikki Krogerus
  2016-12-15 12:06               ` Oliver Neukum
@ 2016-12-15 15:01               ` Guenter Roeck
  2016-12-19 14:45                 ` [RFC PATCH] " Heikki Krogerus
  1 sibling, 1 reply; 19+ messages in thread
From: Guenter Roeck @ 2016-12-15 15:01 UTC (permalink / raw)
  To: Heikki Krogerus, Oliver Neukum; +Cc: Greg KH, linux-kernel, linux-usb

On 12/15/2016 03:50 AM, Heikki Krogerus wrote:
> On Wed, Dec 07, 2016 at 02:52:31PM +0200, Heikki Krogerus wrote:
>> Hi Oliver,
>>
>> On Wed, Dec 07, 2016 at 10:46:48AM +0100, Oliver Neukum wrote:
>>> On Fri, 2016-12-02 at 10:04 -0800, Guenter Roeck wrote:
>>>
>>> Hi,
>>>
>>>> At least for my part I very much concentrated on making sure that
>>>> the user space ABI as well as the port driver API are sane and usable.
>>>
>>> Rightly so, as this part cannot be changed once included in a kernel
>>> release.
>>> So, can we agree that that part at least is ready to go?
>>
>> I at least have no plans on doing any changes to the ABI. The API will
>> change.
>
> I have received (off-list) some questions related to this. There are a
> few things that people would like to still change after all.
>
> 1) Should we use "source" and "sink" instead of "device" and "host"
> with the prefer_role attribute after all? I was uncomfortable with
> that when we talked about it last time because the terms kept changing
> with every new Type-C specification version. But I guess "source" and
> "sink" would make sense in the end. This has been requested by several
> guys now.
>
> 2) Can we change the way we list the supported roles? They are now
> comma separated, but can we use new line instead:
>
>         % cat supported_data_roles
>         host
>         device
>         %
>
> 3) Instead of the "supports_usb_power_delivery" attribute file which
> we have for the ports and partners, it seem it would be more
> interesting to know the supported USB Power Delivery Specification
> version for some, and also USB Type-C Specification version in
> separate attribute file. So basically we would have
> "usb_power_delivery_version" and "usb_typec_version" attributes
> instead or "supports_usb_power_delivery".
>
>
> So would these changes be OK still at this point to everybody?
>

Ok with me.

Guenter

^ permalink raw reply	[flat|nested] 19+ messages in thread

* [RFC PATCH] usb: USB Type-C connector class
  2016-12-15 15:01               ` Guenter Roeck
@ 2016-12-19 14:45                 ` Heikki Krogerus
  2016-12-19 17:22                   ` Guenter Roeck
  0 siblings, 1 reply; 19+ messages in thread
From: Heikki Krogerus @ 2016-12-19 14:45 UTC (permalink / raw)
  To: Guenter Roeck, Oliver Neukum; +Cc: Greg KH, linux-kernel, linux-usb

The purpose of USB Type-C connector class is to provide
unified interface for the user space to get the status and
basic information about USB Type-C connectors on a system,
control over data role swapping, and when the port supports
USB Power Delivery, also control over power role swapping
and Alternate Modes.

Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
---
 Documentation/ABI/testing/sysfs-class-typec |  220 ++++++
 Documentation/usb/typec.txt                 |  174 +++++
 MAINTAINERS                                 |    9 +
 drivers/usb/Kconfig                         |    2 +
 drivers/usb/Makefile                        |    2 +
 drivers/usb/typec/Kconfig                   |    7 +
 drivers/usb/typec/Makefile                  |    1 +
 drivers/usb/typec/typec.c                   | 1047 +++++++++++++++++++++++++++
 include/linux/usb/typec.h                   |  212 ++++++
 9 files changed, 1674 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-class-typec
 create mode 100644 Documentation/usb/typec.txt
 create mode 100644 drivers/usb/typec/Kconfig
 create mode 100644 drivers/usb/typec/Makefile
 create mode 100644 drivers/usb/typec/typec.c
 create mode 100644 include/linux/usb/typec.h


Hi,

There was one more change to the ABI: the ports are now named
"port<id>" instead of "usbc<id>". Let me know if that is OK!

The series is still being reviewed internally, but I'm sending this
already here so you guys can take a look.

The API now expects the drivers to register every part separately,
including the alternate modes for the port itself. Structures for the
partner/cable/plug/altmode are now protected, and the drivers need to
register those with separate descriptor structures and get a handle to
struct typec_partner/cable/plug/altmode if the registration is
successful, or just NULL in case of failure as Greg proposed.

As the drivers need to register the partners and the cables separately
in any case, the drivers do not declare connections with the class
anymore. If the default role changes during initial negotiation with a
partner, the drivers need to notify the class before registering the
partner or cable.


Thanks,


diff --git a/Documentation/ABI/testing/sysfs-class-typec b/Documentation/ABI/testing/sysfs-class-typec
new file mode 100644
index 0000000..663709a
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-typec
@@ -0,0 +1,220 @@
+USB Type-C port devices (eg. /sys/class/typec/port0/)
+
+What:		/sys/class/typec/<port>/current_data_role
+Date:		February 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		The current USB data role the port is operating in. 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.
+
+		Valid values:
+		- host
+		- device
+
+What:		/sys/class/typec/<port>/current_power_role
+Date:		February 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		The current power role of the port. 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.
+
+		Valid values:
+		- source
+		- sink
+
+What:		/sys/class/typec/<port>/vconn_source
+Date:		February 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 are:
+		- 0 when the port is not the VCONN Source
+		- 1 when the port is the VCONN Source
+
+What:		/sys/class/typec/<port>/power_operation_mode
+Date:		February 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows the current power operational mode the port is in.
+
+		Valid values:
+		- USB - Normal power levels defined in USB specifications
+		- USB Type-C 1.5A - Higher 1.5A current defined in USB Type-C
+				    specification.
+		- USB Type-C 3.0A - Higher 3A current defined in USB Type-C
+				    specification.
+                - USB Power Delivery - The voltages and currents defined in USB
+				       Power Delivery specification
+
+What:		/sys/class/typec/<port>/preferred_role
+Date:		February 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 there is no preferred role.
+
+		Valid values:
+		- source
+		- sink
+		- none - to remove preference
+
+What:		/sys/class/typec/<port>/supported_accessory_modes
+Date:		February 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Lists the Accessory Modes, defined in the USB Type-C
+		specification, the port supports.
+
+What:		/sys/class/typec/<port>/supported_data_roles
+Date:		February 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Lists the USB data roles the port is capable of supporting.
+
+		Valid values:
+		- device
+		- host
+		- device, host (DRD as defined in USB Type-C specification v1.2)
+
+What:		/sys/class/typec/<port>/supported_power_roles
+Date:		February 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Lists the power roles the port is capable of supporting.
+
+		Valid values:
+		- source
+		- sink
+		- source, sink (DRP as defined in USB Type-C specification v1.2)
+
+What:		/sys/class/typec/<port>/supports_usb_power_delivery
+Date:		February 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows if the port supports USB Power Delivery.
+		- 1 if USB Power Delivery is supported
+		- 0 when it's not
+
+
+USB Type-C partner devices (eg. /sys/class/typec/port0-partner/)
+
+What:		/sys/class/typec/<port>-partner/accessory_mode
+Date:		February 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows the Accessory Mode name, or "no" when the partner does not
+		support Accesory Modes. The Accessory Modes are defined in USB
+		Type-C Specification.
+
+What:		/sys/class/typec/<port>-partner/supports_usb_power_delivery
+Date:		February 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows if the partner supports USB Power Delivery:
+		- 0 when USB Power Delivery is not supported
+		- 1 when USB Power Delivery is supported
+
+
+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 their alternate modes
+as described in USB Type-C and USB Power Delivery specifications.
+
+What:		/sys/class/typec/<port>-cable/active
+Date:		February 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows if the cable is active or passive.
+
+		Valid values:
+		- 0 when the cable is passive
+		- 1 when the cable is active
+
+What:		/sys/class/typec/<port>-cable/plug_type
+Date:		February 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 - USB Type-C
+		- Captive - Non-standard
+
+What:		/sys/class/typec/<port>-cable/supports_usb_power_delivery
+Date:		February 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows if the cable supports USB Power Delivery:
+		- 0 when USB Power Delivery is not supported
+		- 1 when USB Power Delivery is supported
+
+
+Alternate Mode devices (For example,
+/sys/class/typec/port0-partner/port0-partner.svid:xxxx/). The ports, partners
+and cable plugs can have alternate modes.
+
+What:		/sys/class/typec/<dev>/<dev>.svid:<svid>/<mode>/active
+Date:		February 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:
+		- 0 when the mode is deactive
+		- 1 when the mode is active
+
+What:		/sys/class/typec/<dev>/<dev>.svid:<svid>/<mode>/description
+Date:		February 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/<dev>/<dev>.svid:<svid>/<mode>/vdo
+Date:		February 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows the VDO in hexadecimal returned from the Discover Modes
+		command.
+
+What:		/sys/class/typec/<port>/<port>.svid:<svid>/<mode>/supported_roles
+Date:		February 2017
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows the roles, source or sink, the mode is supported with.
+
+		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.
diff --git a/Documentation/usb/typec.txt b/Documentation/usb/typec.txt
new file mode 100644
index 0000000..e588657
--- /dev/null
+++ b/Documentation/usb/typec.txt
@@ -0,0 +1,174 @@
+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.
+
+
+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. The
+partner attached to port "port0" will be named "port0-partner". Full path to the
+device would be /sys/class/typec/usb0/usb0-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 support Alternate Modes, every Alternate Mode
+SVID will have their own device describing them. The Alternate Modes will not be
+attached to the typec class. For the port's "port0" partner, the Alternate Modes
+would have devices 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/port0.svid:xxxx/mode0/. The
+requests for entering/exiting a mode happens with "active" attribute file in
+that group.
+
+
+API
+---
+
+The functions and structures of the class are defined in
+linux/include/usb/typec.h header.
+
+
+* 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:
+
+struct typec_port *typec_register_port(struct device *parent,
+				       const struct typec_capability *cap);
+
+The class will provide handle to struct typec_port on success and NULL on
+failure. The un-registration of the port happens with the following API:
+
+void typec_unregister_port(struct typec_port *port);
+
+When registering the ports, the prefer_role member in struct typec_capability
+deservers 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.
+
+struct typec_partner *typec_register_partner(struct typec_port *port,
+					     struct typec_partner_desc *desc);
+void typec_unregister_partner(struct typec_partner *partner);
+
+The class will provide a handle to struct typec_partner if the registration was
+successful, or NULL.
+
+
+* 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 maximum two plugs the cable has with the class. Details about the
+cable need to be described in struct typec_cable_desc and about the 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:
+
+struct typec_cable *typec_register_cable(struct typec_port *port,
+					 struct typec_cable_desc *desc);
+void typec_unregister_cable(struct typec_cable *cable);
+
+struct typec_plug *typec_register_plug(struct typec_cable *cable,
+				       struct typec_plug_desc *desc);
+void typec_unregister_plug(struct typec_plug *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.
+
+
+* Notifications
+
+When the partner end 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:
+
+void typec_set_data_role(struct typec_port *, enum typec_data_role);
+void typec_set_pwr_role(struct typec_port *, enum typec_role);
+void typec_set_vconn_role(struct typec_port *, enum typec_role);
+void typec_set_pwr_opmode(struct typec_port *, enum typec_pwr_opmode);
+
+
+* Alternate Modes
+
+USB Type-C Ports, Partners and Cable Plugs may support Alternate Modes with USB
+Type-C. Each Alternate Mode will have SVID, which is either a Standard ID given
+by USB-IF or vendor ID. Each SVID will support 1 - 7 modes. The class provides
+struct typec_mode_desc for describing individual mode of a SVID, and struct
+typec_altmode_desc which is a container of all the modes of a SVID that a
+port, partner or cable plug supports.
+
+Ports that support Alternate Modes need to register each SVID they support with
+the following API:
+
+struct typec_altmode
+*typec_port_register_altmode(struct typec_port *port,
+			     struct typec_altmode_desc *desc);
+
+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
+with the following API.
+
+API for the partners:
+
+struct typec_altmode
+*typec_partner_register_altmode(struct typec_partner *partner,
+				struct typec_altmode_desc *desc);
+
+API for the Cable Plugs:
+
+struct typec_altmode
+*typec_plug_register_altmode(struct typec_plug *plug,
+			     struct typec_altmode_desc *desc);
+
+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:
+
+void typec_unregister_altmode(struct typec_altmode *altmode);
+
+If a partner or cable plug enters or exits a mode, the port driver needs to
+notify the class with the following API:
+
+void typec_altmode_update_active(struct typec_altmode *alt, int mode,
+				 bool active);
diff --git a/MAINTAINERS b/MAINTAINERS
index 10581bd..bcc88cd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12873,6 +12873,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.txt
+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
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index fbe493d..89c322e 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -152,6 +152,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
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index 7791af6..c7f4098 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile
@@ -62,3 +62,5 @@ obj-$(CONFIG_USB_GADGET)	+= gadget/
 obj-$(CONFIG_USB_COMMON)	+= common/
 
 obj-$(CONFIG_USBIP_CORE)	+= usbip/
+
+obj-$(CONFIG_TYPEC)		+= typec/
diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
new file mode 100644
index 0000000..17792f9
--- /dev/null
+++ b/drivers/usb/typec/Kconfig
@@ -0,0 +1,7 @@
+
+menu "USB Power Delivery and Type-C drivers"
+
+config TYPEC
+	tristate
+
+endmenu
diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
new file mode 100644
index 0000000..1012a8b
--- /dev/null
+++ b/drivers/usb/typec/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_TYPEC)		+= typec.o
diff --git a/drivers/usb/typec/typec.c b/drivers/usb/typec/typec.c
new file mode 100644
index 0000000..0e9dd2e
--- /dev/null
+++ b/drivers/usb/typec/typec.c
@@ -0,0 +1,1047 @@
+/*
+ * USB Type-C Connector Class
+ *
+ * Copyright (C) 2017, Intel Corporation
+ * Author: Heikki Krogerus <heikki.krogerus@linux.intel.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 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb/typec.h>
+
+/* XXX: Once we have a header for USB Power Delivery, this belongs there */
+#define ALTMODE_MAX_N_MODES	7
+
+struct typec_mode {
+	int			index;
+	u32			vdo;
+	char			*desc;
+	enum typec_port_type	roles;
+
+	struct typec_altmode	*alt_mode;
+
+	unsigned int		active:1;
+
+	char			group_name[6];
+	struct attribute_group	group;
+	struct attribute	*attrs[5];
+	struct device_attribute vdo_attr;
+	struct device_attribute desc_attr;
+	struct device_attribute active_attr;
+	struct device_attribute roles_attr;
+};
+
+struct typec_altmode {
+	struct device			dev;
+	u16				svid;
+	int				n_modes;
+	struct typec_mode		modes[ALTMODE_MAX_N_MODES];
+	const struct attribute_group	*mode_groups[ALTMODE_MAX_N_MODES];
+};
+
+struct typec_plug {
+	struct device			dev;
+	enum typec_plug_index		index;
+};
+
+struct typec_cable {
+	struct device			dev;
+	u16				pd_revision;
+	enum typec_plug_type		type;
+	u32				vdo;
+	unsigned int			active:1;
+};
+
+struct typec_partner {
+	struct device			dev;
+	u16				pd_revision;
+	u32				vdo;
+	enum typec_accessory		accessory;
+};
+
+struct typec_port {
+	unsigned int			id;
+	struct device			dev;
+
+	int				prefer_role;
+	enum typec_data_role		data_role;
+	enum typec_role			pwr_role;
+	enum typec_role			vconn_role;
+	enum typec_pwr_opmode		pwr_opmode;
+
+	const struct typec_capability	*cap;
+};
+
+#define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev)
+#define to_typec_plug(_dev_) container_of(_dev_, struct typec_plug, dev)
+#define to_typec_cable(_dev_) container_of(_dev_, struct typec_cable, dev)
+#define to_typec_partner(_dev_) container_of(_dev_, struct typec_partner, dev)
+#define to_altmode(_dev_) container_of(_dev_, struct typec_altmode, dev)
+
+static struct device_type typec_partner_dev_type;
+static struct device_type typec_cable_dev_type;
+static struct device_type typec_plug_dev_type;
+static struct device_type typec_port_dev_type;
+
+#define is_typec_partner(_dev_) (_dev_->type == &typec_partner_dev_type)
+#define is_typec_cable(_dev_) (_dev_->type == &typec_cable_dev_type)
+#define is_typec_plug(_dev_) (_dev_->type == &typec_plug_dev_type)
+#define is_typec_port(_dev_) (_dev_->type == &typec_port_dev_type)
+
+static DEFINE_IDA(typec_index_ida);
+static struct class *typec_class;
+
+/* Common attributes */
+
+static const char * const typec_accessory_modes[] = {
+	[TYPEC_ACCESSORY_NONE]	= "",
+	[TYPEC_ACCESSORY_AUDIO]	= "Audio Adapter Accessory Mode",
+	[TYPEC_ACCESSORY_DEBUG]	= "Debug Accessory Mode",
+};
+
+static ssize_t usb_power_delivery_revision_show(struct device *dev,
+						struct device_attribute *attr,
+						char *buf)
+{
+	u16 rev = 0;
+
+	if (is_typec_partner(dev)) {
+		struct typec_partner *p = to_typec_partner(dev);
+
+		rev = p->pd_revision;
+	} else if (is_typec_cable(dev)) {
+		struct typec_cable *p = to_typec_cable(dev);
+
+		rev = p->pd_revision;
+	} else if (is_typec_port(dev)) {
+		struct typec_port *p = to_typec_port(dev);
+
+		rev = p->cap->pd_revision;
+	}
+
+	return sprintf(buf, "%d\n", (rev >> 8) & 0xff);
+}
+static DEVICE_ATTR_RO(usb_power_delivery_revision);
+
+static ssize_t
+vdo_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	u32 vdo = 0;
+
+	if (is_typec_partner(dev)) {
+		struct typec_partner *p = to_typec_partner(dev);
+
+		vdo = p->vdo;
+	} else if (is_typec_cable(dev)) {
+		struct typec_cable *p = to_typec_cable(dev);
+
+		vdo = p->vdo;
+	}
+
+	return sprintf(buf, "0x%08x\n", vdo);
+}
+static DEVICE_ATTR_RO(vdo);
+
+/* ------------------------------------------------------------------------- */
+/* Alternate Modes */
+
+/*
+ * typec_altmode_update_active - Notify about Enter/Exit mode
+ * @alt: Handle to the Alternate Mode
+ * @mode: Mode index
+ * @active: True when the mode has been enterred
+ */
+void typec_altmode_update_active(struct typec_altmode *alt, int mode,
+				 bool active)
+{
+	struct typec_mode *m = &alt->modes[mode];
+	char dir[6];
+
+	if (m->active == active)
+		return;
+
+	m->active = active;
+	snprintf(dir, 6, "mode%d", mode);
+	sysfs_notify(&alt->dev.kobj, dir, "active");
+	kobject_uevent(&alt->dev.kobj, KOBJ_CHANGE);
+}
+EXPORT_SYMBOL_GPL(typec_altmode_update_active);
+
+/*
+ * typec_altmode2port - Alternate Mode to USB Type-C port
+ * @alt: The Alternate Mode
+ *
+ * Returns the port that the cable plug or partner with @alt is connected to.
+ */
+struct typec_port *typec_altmode2port(struct typec_altmode *alt)
+{
+	if (is_typec_plug(alt->dev.parent))
+		return to_typec_port(alt->dev.parent->parent->parent);
+	if (is_typec_partner(alt->dev.parent))
+		return to_typec_port(alt->dev.parent->parent);
+	if (is_typec_port(alt->dev.parent))
+		return to_typec_port(alt->dev.parent);
+
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(typec_altmode2port);
+
+static void typec_altmode_release(struct device *dev)
+{
+	struct typec_altmode *alt = to_altmode(dev);
+	int i;
+
+	for (i = 0; i < alt->n_modes; i++)
+		kfree(alt->modes[i].desc);
+	kfree(alt);
+}
+
+static ssize_t
+typec_altmode_vdo_show(struct device *dev, struct device_attribute *attr,
+		       char *buf)
+{
+	struct typec_mode *mode = container_of(attr, struct typec_mode,
+					       vdo_attr);
+
+	return sprintf(buf, "0x%08x\n", mode->vdo);
+}
+
+static ssize_t
+typec_altmode_desc_show(struct device *dev, struct device_attribute *attr,
+			char *buf)
+{
+	struct typec_mode *mode = container_of(attr, struct typec_mode,
+					       desc_attr);
+
+	return sprintf(buf, "%s\n", mode->desc ? mode->desc : "");
+}
+
+static ssize_t
+typec_altmode_active_show(struct device *dev, struct device_attribute *attr,
+			  char *buf)
+{
+	struct typec_mode *mode = container_of(attr, struct typec_mode,
+					       active_attr);
+
+	return sprintf(buf, "%d\n", mode->active);
+}
+
+static ssize_t
+typec_altmode_active_store(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t size)
+{
+	struct typec_mode *mode = container_of(attr, struct typec_mode,
+					       active_attr);
+	struct typec_port *port = typec_altmode2port(mode->alt_mode);
+	bool activate;
+	int ret;
+
+	if (!port->cap->activate_mode)
+		return -EOPNOTSUPP;
+
+	ret = kstrtobool(buf, &activate);
+	if (ret)
+		return ret;
+
+	ret = port->cap->activate_mode(port->cap, mode->index, activate);
+	if (ret)
+		return ret;
+
+	return size;
+}
+
+static ssize_t
+typec_altmode_roles_show(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct typec_mode *mode = container_of(attr, struct typec_mode,
+					       roles_attr);
+	ssize_t ret;
+
+	switch (mode->roles) {
+	case TYPEC_PORT_DFP:
+		ret =  sprintf(buf, "source\n");
+		break;
+	case TYPEC_PORT_UFP:
+		ret = sprintf(buf, "sink\n");
+		break;
+	case TYPEC_PORT_DRP:
+	default:
+		ret = sprintf(buf, "source\nsink\n");
+		break;
+	}
+	return ret;
+}
+
+static inline void typec_init_modes(struct typec_altmode *alt,
+				    struct typec_mode_desc *desc, bool is_port)
+{
+	int i;
+
+	for (i = 0; i < alt->n_modes; i++, desc++) {
+		struct typec_mode *mode = &alt->modes[i];
+
+		/* Not considering the human readable description critical */
+		mode->desc = kstrdup(desc->desc, GFP_KERNEL);
+		if (desc->desc && !mode->desc)
+			dev_err(&alt->dev, "failed to copy mode%d desc\n", i);
+
+		mode->alt_mode = alt;
+		mode->vdo = desc->vdo;
+		mode->roles = desc->roles;
+		mode->index = desc->index;
+		sprintf(mode->group_name, "mode%d", desc->index);
+
+		sysfs_attr_init(&mode->vdo_attr.attr);
+		mode->vdo_attr.attr.name = "vdo";
+		mode->vdo_attr.attr.mode = 0444;
+		mode->vdo_attr.show = typec_altmode_vdo_show;
+
+		sysfs_attr_init(&mode->desc_attr.attr);
+		mode->desc_attr.attr.name = "description";
+		mode->desc_attr.attr.mode = 0444;
+		mode->desc_attr.show = typec_altmode_desc_show;
+
+		sysfs_attr_init(&mode->active_attr.attr);
+		mode->active_attr.attr.name = "active";
+		mode->active_attr.attr.mode = 0644;
+		mode->active_attr.show = typec_altmode_active_show;
+		mode->active_attr.store = typec_altmode_active_store;
+
+		mode->attrs[0] = &mode->vdo_attr.attr;
+		mode->attrs[1] = &mode->desc_attr.attr;
+		mode->attrs[2] = &mode->active_attr.attr;
+
+		/* With ports, list the roles that the mode is supported with */
+		if (is_port) {
+			sysfs_attr_init(&mode->roles_attr.attr);
+			mode->roles_attr.attr.name = "supported_roles";
+			mode->roles_attr.attr.mode = 0444;
+			mode->roles_attr.show = typec_altmode_roles_show;
+
+			mode->attrs[3] = &mode->roles_attr.attr;
+		}
+
+		mode->group.attrs = mode->attrs;
+		mode->group.name = mode->group_name;
+
+		alt->mode_groups[i] = &mode->group;
+	}
+}
+
+static struct typec_altmode
+*typec_register_altmode(struct device *parent, struct typec_altmode_desc *desc)
+{
+	struct typec_altmode *alt;
+	int ret;
+
+	alt = kzalloc(sizeof(*alt), GFP_KERNEL);
+	if (!alt)
+		return NULL;
+
+	alt->svid = desc->svid;
+	alt->n_modes = desc->n_modes;
+	typec_init_modes(alt, desc->modes, is_typec_port(parent));
+
+	alt->dev.parent = parent;
+	alt->dev.groups = alt->mode_groups;
+	alt->dev.release = typec_altmode_release;
+	dev_set_name(&alt->dev, "%s.svid:%04x", dev_name(parent), alt->svid);
+
+	ret = device_register(&alt->dev);
+	if (ret) {
+		int i;
+
+		dev_err(parent, "failed to register alternate mode (%d)\n",
+			ret);
+
+		put_device(&alt->dev);
+
+		for (i = 0; i < alt->n_modes; i++)
+			kfree(alt->modes[i].desc);
+		kfree(alt);
+		return NULL;
+	}
+
+	return alt;
+}
+
+void typec_unregister_altmode(struct typec_altmode *alt)
+{
+	if (alt)
+		device_unregister(&alt->dev);
+}
+EXPORT_SYMBOL_GPL(typec_unregister_altmode);
+
+/* ------------------------------------------------------------------------- */
+/* Type-C Partners */
+
+static ssize_t accessory_mode_show(struct device *dev,
+				   struct device_attribute *attr,
+				   char *buf)
+{
+	struct typec_partner *p = to_typec_partner(dev);
+
+	return sprintf(buf, "%s\n", typec_accessory_modes[p->accessory]);
+}
+static DEVICE_ATTR_RO(accessory_mode);
+
+static struct attribute *typec_partner_attrs[] = {
+	&dev_attr_vdo.attr,
+	&dev_attr_accessory_mode.attr,
+	&dev_attr_usb_power_delivery_revision.attr,
+	NULL
+};
+ATTRIBUTE_GROUPS(typec_partner);
+
+static void typec_partner_release(struct device *dev)
+{
+	struct typec_partner *partner = to_typec_partner(dev);
+
+	kfree(partner);
+}
+
+static struct device_type typec_partner_dev_type = {
+	.name = "typec_partner_device",
+	.groups = typec_partner_groups,
+	.release = typec_partner_release,
+};
+
+struct typec_altmode
+*typec_partner_register_altmode(struct typec_partner *partner,
+				struct typec_altmode_desc *desc)
+{
+	return typec_register_altmode(&partner->dev, desc);
+}
+EXPORT_SYMBOL_GPL(typec_partner_register_altmode);
+
+struct typec_partner *typec_register_partner(struct typec_port *port,
+					     struct typec_partner_desc *desc)
+{
+	struct typec_partner *partner = NULL;
+	int ret;
+
+	partner = kzalloc(sizeof(*partner), GFP_KERNEL);
+	if (!partner)
+		return NULL;
+
+	partner->vdo = desc->vdo;
+	partner->accessory = desc->accessory;
+	partner->pd_revision = desc->pd_revision;
+
+	partner->dev.class = typec_class;
+	partner->dev.parent = &port->dev;
+	partner->dev.type = &typec_partner_dev_type;
+	dev_set_name(&partner->dev, "%s-partner", dev_name(&port->dev));
+
+	ret = device_register(&partner->dev);
+	if (ret) {
+		dev_err(&port->dev, "failed to register partner (%d)\n", ret);
+		put_device(&partner->dev);
+		kfree(partner);
+		return NULL;
+	}
+
+	return partner;
+}
+EXPORT_SYMBOL_GPL(typec_register_partner);
+
+void typec_unregister_partner(struct typec_partner *partner)
+{
+	if (partner)
+		device_unregister(&partner->dev);
+}
+EXPORT_SYMBOL_GPL(typec_unregister_partner);
+
+/* ------------------------------------------------------------------------- */
+/* Type-C Cable Plugs */
+
+static void typec_plug_release(struct device *dev)
+{
+	struct typec_plug *plug = to_typec_plug(dev);
+
+	kfree(plug);
+}
+
+static struct device_type typec_plug_dev_type = {
+	.name = "typec_plug_device",
+	.release = typec_plug_release,
+};
+
+struct typec_altmode
+*typec_plug_register_altmode(struct typec_plug *plug,
+			     struct typec_altmode_desc *desc)
+{
+	return typec_register_altmode(&plug->dev, desc);
+}
+EXPORT_SYMBOL_GPL(typec_plug_register_altmode);
+
+struct typec_plug *typec_register_plug(struct typec_cable *cable,
+				       struct typec_plug_desc *desc)
+{
+	struct typec_plug *plug = NULL;
+	char name[8];
+	int ret;
+
+	plug = kzalloc(sizeof(*plug), GFP_KERNEL);
+	if (!plug)
+		return NULL;
+
+	sprintf(name, "plug%d", desc->index);
+
+	plug->index = desc->index;
+	plug->dev.class = typec_class;
+	plug->dev.parent = &cable->dev;
+	plug->dev.type = &typec_plug_dev_type;
+	dev_set_name(&plug->dev, "%s-%s", dev_name(cable->dev.parent), name);
+
+	ret = device_register(&plug->dev);
+	if (ret) {
+		dev_err(&cable->dev, "failed to register plug (%d)\n", ret);
+		put_device(&plug->dev);
+		kfree(plug);
+		return NULL;
+	}
+
+	return plug;
+}
+EXPORT_SYMBOL_GPL(typec_register_plug);
+
+void typec_unregister_plug(struct typec_plug *plug)
+{
+	if (plug)
+		device_unregister(&plug->dev);
+}
+EXPORT_SYMBOL_GPL(typec_unregister_plug);
+
+/* Type-C Cables */
+
+static ssize_t
+active_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct typec_cable *cable = to_typec_cable(dev);
+
+	return sprintf(buf, "%d\n", cable->active);
+}
+static DEVICE_ATTR_RO(active);
+
+static const char * const typec_plug_types[] = {
+	[USB_PLUG_NONE]		= "unknown",
+	[USB_PLUG_TYPE_A]	= "Type-A",
+	[USB_PLUG_TYPE_B]	= "Type-B",
+	[USB_PLUG_TYPE_C]	= "Type-C",
+	[USB_PLUG_CAPTIVE]	= "Captive",
+};
+
+static ssize_t plug_type_show(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct typec_cable *cable = to_typec_cable(dev);
+
+	return sprintf(buf, "%s\n", typec_plug_types[cable->type]);
+}
+static DEVICE_ATTR_RO(plug_type);
+
+static struct attribute *typec_cable_attrs[] = {
+	&dev_attr_active.attr,
+	&dev_attr_plug_type.attr,
+	&dev_attr_usb_power_delivery_revision.attr,
+	NULL
+};
+ATTRIBUTE_GROUPS(typec_cable);
+
+static void typec_cable_release(struct device *dev)
+{
+	struct typec_cable *cable = to_typec_cable(dev);
+
+	kfree(cable);
+}
+
+static struct device_type typec_cable_dev_type = {
+	.name = "typec_cable_device",
+	.groups = typec_cable_groups,
+	.release = typec_cable_release,
+};
+
+struct typec_cable *typec_register_cable(struct typec_port *port,
+					 struct typec_cable_desc *desc)
+{
+	struct typec_cable *cable = NULL;
+	int ret;
+
+	cable = kzalloc(sizeof(*cable), GFP_KERNEL);
+	if (!cable)
+		return NULL;
+
+	cable->type = desc->type;
+	cable->vdo = desc->vdo;
+	cable->active = desc->active;
+	cable->pd_revision = desc->pd_revision;
+
+	cable->dev.class = typec_class;
+	cable->dev.parent = &port->dev;
+	cable->dev.type = &typec_cable_dev_type;
+	dev_set_name(&cable->dev, "%s-cable", dev_name(&port->dev));
+
+	ret = device_register(&cable->dev);
+	if (ret) {
+		dev_err(&port->dev, "failed to register cable (%d)\n", ret);
+		put_device(&cable->dev);
+		kfree(cable);
+		return NULL;
+	}
+
+	return cable;
+}
+EXPORT_SYMBOL_GPL(typec_register_cable);
+
+void typec_unregister_cable(struct typec_cable *cable)
+{
+	if (cable)
+		device_unregister(&cable->dev);
+}
+EXPORT_SYMBOL_GPL(typec_unregister_cable);
+
+/* ------------------------------------------------------------------------- */
+/* USB Type-C ports */
+
+/* --------------------------------------- */
+/* Driver callbacks to report role updates */
+
+void typec_set_data_role(struct typec_port *port, enum typec_data_role role)
+{
+	if (port->data_role == role)
+		return;
+
+	port->data_role = role;
+	sysfs_notify(&port->dev.kobj, NULL, "current_data_role");
+	kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
+}
+EXPORT_SYMBOL_GPL(typec_set_data_role);
+
+void typec_set_pwr_role(struct typec_port *port, enum typec_role role)
+{
+	if (port->pwr_role == role)
+		return;
+
+	port->pwr_role = role;
+	sysfs_notify(&port->dev.kobj, NULL, "current_power_role");
+	kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
+}
+EXPORT_SYMBOL_GPL(typec_set_pwr_role);
+
+void typec_set_vconn_role(struct typec_port *port, enum typec_role role)
+{
+	if (port->vconn_role == role)
+		return;
+
+	port->vconn_role = role;
+	sysfs_notify(&port->dev.kobj, NULL, "current_vconn_role");
+	kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
+}
+EXPORT_SYMBOL_GPL(typec_set_vconn_role);
+
+void typec_set_pwr_opmode(struct typec_port *port,
+			  enum typec_pwr_opmode opmode)
+{
+	if (port->pwr_opmode == opmode)
+		return;
+
+	port->pwr_opmode = opmode;
+	sysfs_notify(&port->dev.kobj, NULL, "power_operation_mode");
+	kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
+}
+EXPORT_SYMBOL_GPL(typec_set_pwr_opmode);
+
+/* --------------------------------------- */
+
+static const char * const typec_roles[] = {
+	[TYPEC_SINK]	= "sink",
+	[TYPEC_SOURCE]	= "source",
+};
+
+static const char * const typec_data_roles[] = {
+	[TYPEC_DEVICE]	= "device",
+	[TYPEC_HOST]	= "host",
+};
+
+static ssize_t
+preferred_role_store(struct device *dev, struct device_attribute *attr,
+		     const char *buf, size_t size)
+{
+	struct typec_port *port = to_typec_port(dev);
+	int role;
+	int ret;
+
+	if (port->cap->type != TYPEC_PORT_DRP) {
+		dev_dbg(dev, "Preferred role only supported with DRP ports\n");
+		return -EOPNOTSUPP;
+	}
+
+	if (!port->cap->try_role) {
+		dev_dbg(dev, "Setting preferred role not supported\n");
+		return -EOPNOTSUPP;
+	}
+
+	role = sysfs_match_string(typec_roles, buf);
+	if (role < 0) {
+		if (sysfs_streq(buf, "none"))
+			role = TYPEC_NO_PREFERRED_ROLE;
+		else
+			return -EINVAL;
+	}
+
+	ret = port->cap->try_role(port->cap, role);
+	if (ret)
+		return ret;
+
+	port->prefer_role = role;
+	return size;
+}
+
+static ssize_t
+preferred_role_show(struct device *dev, struct device_attribute *attr,
+		    char *buf)
+{
+	struct typec_port *port = to_typec_port(dev);
+
+	if (port->cap->type != TYPEC_PORT_DRP)
+		return 0;
+
+	if (port->prefer_role < 0)
+		return 0;
+
+	return sprintf(buf, "%s\n", typec_roles[port->prefer_role]);
+}
+static DEVICE_ATTR_RW(preferred_role);
+
+static ssize_t
+current_data_role_store(struct device *dev, struct device_attribute *attr,
+			const char *buf, size_t size)
+{
+	struct typec_port *port = to_typec_port(dev);
+	int ret;
+
+	if (port->cap->type != TYPEC_PORT_DRP) {
+		dev_dbg(dev, "data role swap only supported with DRP ports\n");
+		return -EOPNOTSUPP;
+	}
+
+	if (!port->cap->dr_set) {
+		dev_dbg(dev, "data role swapping not supported\n");
+		return -EOPNOTSUPP;
+	}
+
+	ret = sysfs_match_string(typec_data_roles, buf);
+	if (ret < 0)
+		return ret;
+
+	ret = port->cap->dr_set(port->cap, ret);
+	if (ret)
+		return ret;
+
+	return size;
+}
+
+static ssize_t
+current_data_role_show(struct device *dev, struct device_attribute *attr,
+		       char *buf)
+{
+	struct typec_port *port = to_typec_port(dev);
+
+	return sprintf(buf, "%s\n", typec_data_roles[port->data_role]);
+}
+static DEVICE_ATTR_RW(current_data_role);
+
+static ssize_t supported_data_roles_show(struct device *dev,
+					 struct device_attribute *attr,
+					 char *buf)
+{
+	struct typec_port *port = to_typec_port(dev);
+
+	if (port->cap->type == TYPEC_PORT_DRP)
+		return sprintf(buf, "host\ndevice\n");
+
+	return sprintf(buf, "%s\n", typec_data_roles[port->data_role]);
+}
+static DEVICE_ATTR_RO(supported_data_roles);
+
+static ssize_t current_power_role_store(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t size)
+{
+	struct typec_port *port = to_typec_port(dev);
+	int ret = size;
+
+	if (!port->cap->pd_revision) {
+		dev_dbg(dev, "power role swap only supported with USB PD\n");
+		return -EOPNOTSUPP;
+	}
+
+	if (!port->cap->pr_set) {
+		dev_dbg(dev, "power role swapping not supported\n");
+		return -EOPNOTSUPP;
+	}
+
+	if (port->pwr_opmode != TYPEC_PWR_MODE_PD) {
+		dev_dbg(dev, "partner unable to swap power role\n");
+		return -EIO;
+	}
+
+	ret = sysfs_match_string(typec_roles, buf);
+	if (ret < 0)
+		return ret;
+
+	ret = port->cap->pr_set(port->cap, ret);
+	if (ret)
+		return ret;
+
+	return size;
+}
+
+static ssize_t current_power_role_show(struct device *dev,
+				       struct device_attribute *attr, char *buf)
+{
+	struct typec_port *port = to_typec_port(dev);
+
+	return sprintf(buf, "%s\n", typec_roles[port->pwr_role]);
+}
+static DEVICE_ATTR_RW(current_power_role);
+
+static ssize_t supported_power_roles_show(struct device *dev,
+					  struct device_attribute *attr,
+					  char *buf)
+{
+	struct typec_port *port = to_typec_port(dev);
+
+	if (port->cap->pd_revision || port->cap->type == TYPEC_PORT_DRP)
+		return sprintf(buf, "source\nsink\n");
+
+	return sprintf(buf, "%s\n", typec_roles[port->pwr_role]);
+}
+static DEVICE_ATTR_RO(supported_power_roles);
+
+static const char * const typec_pwr_opmodes[] = {
+	[TYPEC_PWR_MODE_USB]	= "USB",
+	[TYPEC_PWR_MODE_1_5A]	= "USB Type-C 1.5A",
+	[TYPEC_PWR_MODE_3_0A]	= "USB Type-C 3.0A",
+	[TYPEC_PWR_MODE_PD]	= "USB Power Delivery",
+};
+
+static ssize_t power_operation_mode_show(struct device *dev,
+					 struct device_attribute *attr,
+					 char *buf)
+{
+	struct typec_port *port = to_typec_port(dev);
+
+	return sprintf(buf, "%s\n", typec_pwr_opmodes[port->pwr_opmode]);
+}
+static DEVICE_ATTR_RO(power_operation_mode);
+
+static ssize_t vconn_source_store(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t size)
+{
+	struct typec_port *port = to_typec_port(dev);
+	enum typec_role role;
+	int ret;
+
+	if (!port->cap->pd_revision) {
+		dev_dbg(dev, "vconn swap only supported with USB PD\n");
+		return -EOPNOTSUPP;
+	}
+
+	if (!port->cap->vconn_set) {
+		dev_dbg(dev, "vconn swapping not supported\n");
+		return -EOPNOTSUPP;
+	}
+
+	if (sysfs_streq(buf, "1"))
+		role = TYPEC_SOURCE;
+	else if (sysfs_streq(buf, "0"))
+		role = TYPEC_SINK;
+	else
+		return -EINVAL;
+
+	ret = port->cap->vconn_set(port->cap, role);
+	if (ret)
+		return ret;
+
+	return size;
+}
+
+static ssize_t vconn_source_show(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct typec_port *port = to_typec_port(dev);
+
+	return sprintf(buf, "%d\n", port->vconn_role == TYPEC_SOURCE ? 1 : 0);
+}
+static DEVICE_ATTR_RW(vconn_source);
+
+static ssize_t supported_accessory_modes_show(struct device *dev,
+					      struct device_attribute *attr,
+					      char *buf)
+{
+	struct typec_port *port = to_typec_port(dev);
+	ssize_t ret = 0;
+	int i;
+
+	if (!port->cap->accessory)
+		return 0;
+
+	for (i = 0; port->cap->accessory[i]; i++)
+		ret += sprintf(buf + ret, "%s\n",
+			       typec_accessory_modes[port->cap->accessory[i]]);
+	return ret;
+}
+static DEVICE_ATTR_RO(supported_accessory_modes);
+
+static ssize_t usb_typec_revision_show(struct device *dev,
+				       struct device_attribute *attr,
+				       char *buf)
+{
+	struct typec_port *port = to_typec_port(dev);
+	u16 rev = port->cap->revision;
+
+	return sprintf(buf, "%d.%d\n", (rev >> 8) & 0xff, rev >> 4 & 0xf);
+}
+static DEVICE_ATTR_RO(usb_typec_revision);
+
+static struct attribute *typec_attrs[] = {
+	&dev_attr_current_power_role.attr,
+	&dev_attr_current_data_role.attr,
+	&dev_attr_power_operation_mode.attr,
+	&dev_attr_preferred_role.attr,
+	&dev_attr_supported_accessory_modes.attr,
+	&dev_attr_supported_data_roles.attr,
+	&dev_attr_supported_power_roles.attr,
+	&dev_attr_usb_power_delivery_revision.attr,
+	&dev_attr_usb_typec_revision.attr,
+	&dev_attr_vconn_source.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(typec);
+
+static int typec_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+	int ret;
+
+	ret = add_uevent_var(env, "TYPEC_PORT=%s", dev_name(dev));
+	if (ret)
+		dev_err(dev, "failed to add uevent TYPEC_PORT\n");
+
+	return ret;
+}
+
+static void typec_release(struct device *dev)
+{
+	struct typec_port *port = to_typec_port(dev);
+
+	ida_simple_remove(&typec_index_ida, port->id);
+	kfree(port);
+}
+
+static struct device_type typec_port_dev_type = {
+	.name = "typec_port",
+	.groups = typec_groups,
+	.uevent = typec_uevent,
+	.release = typec_release,
+};
+
+struct typec_altmode
+*typec_port_register_altmode(struct typec_port *port,
+			     struct typec_altmode_desc *desc)
+{
+	return typec_register_altmode(&port->dev, desc);
+}
+EXPORT_SYMBOL_GPL(typec_port_register_altmode);
+
+struct typec_port *typec_register_port(struct device *parent,
+				       const struct typec_capability *cap)
+{
+	struct typec_port *port;
+	enum typec_role role;
+	int ret;
+	int id;
+
+	port = kzalloc(sizeof(*port), GFP_KERNEL);
+	if (!port)
+		return NULL;
+
+	id = ida_simple_get(&typec_index_ida, 0, 0, GFP_KERNEL);
+	if (id < 0) {
+		kfree(port);
+		return NULL;
+	}
+
+	if (cap->type == TYPEC_PORT_DFP)
+		role = TYPEC_SOURCE;
+	else if (cap->type == TYPEC_PORT_UFP)
+		role = TYPEC_SINK;
+	else
+		role = cap->prefer_role;
+
+	if (role == TYPEC_SOURCE) {
+		port->data_role = TYPEC_HOST;
+		port->pwr_role = TYPEC_SOURCE;
+		port->vconn_role = TYPEC_SOURCE;
+	} else {
+		port->data_role = TYPEC_DEVICE;
+		port->pwr_role = TYPEC_SINK;
+		port->vconn_role = TYPEC_SINK;
+	}
+
+	port->id = id;
+	port->cap = cap;
+	port->prefer_role = cap->prefer_role;
+
+	port->dev.type = &typec_port_dev_type;
+	port->dev.class = typec_class;
+	port->dev.parent = parent;
+	dev_set_name(&port->dev, "port%d", id);
+
+	ret = device_register(&port->dev);
+	if (ret) {
+		dev_err(parent, "failed to register port (%d)\n", ret);
+		ida_simple_remove(&typec_index_ida, id);
+		put_device(&port->dev);
+		kfree(port);
+		return NULL;
+	}
+
+	return port;
+}
+EXPORT_SYMBOL_GPL(typec_register_port);
+
+void typec_unregister_port(struct typec_port *port)
+{
+	if (port)
+		device_unregister(&port->dev);
+}
+EXPORT_SYMBOL_GPL(typec_unregister_port);
+
+static int __init typec_init(void)
+{
+	typec_class = class_create(THIS_MODULE, "typec");
+	if (IS_ERR(typec_class))
+		return PTR_ERR(typec_class);
+	return 0;
+}
+subsys_initcall(typec_init);
+
+static void __exit typec_exit(void)
+{
+	class_destroy(typec_class);
+	ida_destroy(&typec_index_ida);
+}
+module_exit(typec_exit);
+
+MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("USB Type-C Connector Class");
diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
new file mode 100644
index 0000000..37b996c
--- /dev/null
+++ b/include/linux/usb/typec.h
@@ -0,0 +1,212 @@
+
+#ifndef __LINUX_USB_TYPEC_H
+#define __LINUX_USB_TYPEC_H
+
+#include <linux/types.h>
+
+/* USB Type-C Specification releases */
+#define USB_TYPEC_REV_1_0	0x100 /* 1.0 */
+#define USB_TYPEC_REV_1_1	0x110 /* 1.1 */
+#define USB_TYPEC_REV_1_2	0x120 /* 1.2 */
+
+struct typec_altmode;
+struct typec_partner;
+struct typec_cable;
+struct typec_plug;
+struct typec_port;
+
+enum typec_port_type {
+	TYPEC_PORT_DFP,
+	TYPEC_PORT_UFP,
+	TYPEC_PORT_DRP,
+};
+
+enum typec_plug_type {
+	USB_PLUG_NONE,
+	USB_PLUG_TYPE_A,
+	USB_PLUG_TYPE_B,
+	USB_PLUG_TYPE_C,
+	USB_PLUG_CAPTIVE,
+};
+
+enum typec_data_role {
+	TYPEC_DEVICE,
+	TYPEC_HOST,
+};
+
+enum typec_role {
+	TYPEC_SINK,
+	TYPEC_SOURCE,
+};
+
+enum typec_pwr_opmode {
+	TYPEC_PWR_MODE_USB,
+	TYPEC_PWR_MODE_1_5A,
+	TYPEC_PWR_MODE_3_0A,
+	TYPEC_PWR_MODE_PD,
+};
+
+enum typec_accessory {
+	TYPEC_ACCESSORY_NONE,
+	TYPEC_ACCESSORY_AUDIO,
+	TYPEC_ACCESSORY_DEBUG,
+};
+
+/*
+ * struct typec_mode_desc - Individual Mode of an Alternate Mode
+ * @index: Index of the Mode within the SVID
+ * @vdo: VDO returned by Discover Modes USB PD command
+ * @desc: Optional human readable description of the mode
+ * @roles: Only for ports. DRP if the mode is available in both roles
+ *
+ * Description of a mode of an Alternate Mode which a connector, cable plug or
+ * partner supports. Every mode will have it's own sysfs group. The details are
+ * the VDO returned by discover modes command, description for the mode and
+ * active flag telling has the mode being entered or not.
+ */
+struct typec_mode_desc {
+	int			index;
+	u32			vdo;
+	char			*desc;
+	/* Only used with ports */
+	enum typec_port_type	roles;
+};
+
+/*
+ * struct typec_altmode_desc - USB Type-C Alternate Mode Descriptor
+ * @svid: Standard or Vendor ID
+ * @n_modes: Number of modes
+ * @modes: Array of modes supported by the Alternate Mode
+ *
+ * Representation of an Alternate Mode that has SVID assigned by USB-IF. The
+ * array of modes will list the modes of a particular SVID that are supported by
+ * a connector, partner of a cable plug.
+ */
+struct typec_altmode_desc {
+	u16			svid;
+	int			n_modes;
+	struct typec_mode_desc	*modes;
+};
+
+struct typec_altmode
+*typec_partner_register_altmode(struct typec_partner *partner,
+				struct typec_altmode_desc *desc);
+struct typec_altmode
+*typec_plug_register_altmode(struct typec_plug *plug,
+			     struct typec_altmode_desc *desc);
+struct typec_altmode
+*typec_port_register_altmode(struct typec_port *port,
+			     struct typec_altmode_desc *desc);
+void typec_unregister_altmode(struct typec_altmode *altmode);
+
+struct typec_port *typec_altmode2port(struct typec_altmode *alt);
+
+void typec_altmode_update_active(struct typec_altmode *alt, int mode,
+				 bool active);
+
+enum typec_plug_index {
+	TYPEC_PLUG_SOP_P,
+	TYPEC_PLUG_SOP_PP,
+};
+
+/*
+ * struct typec_plug_desc - USB Type-C Cable Plug Descriptor
+ * @index: SOP Prime for the plug connected to DFP and SOP Double Prime for the
+ *         plug connected to UFP
+ *
+ * Represents USB Type-C Cable Plug.
+ */
+struct typec_plug_desc {
+	enum typec_plug_index	index;
+};
+
+/*
+ * struct typec_cable_desc - USB Type-C Cable Descriptor
+ * @pd_revision: USB Power Delivery Specification revision
+ * @type: The plug type from USB PD Cable VDO
+ * @active: Is the cable active or passive
+ *
+ * Represents USB Type-C Cable attached to USB Type-C port.
+ */
+struct typec_cable_desc {
+	u16			pd_revision; /* 0300H = "3.0" */
+	enum typec_plug_type	type;
+	u32			vdo;
+	unsigned int		active:1;
+};
+
+/*
+ * struct typec_partner_desc - USB Type-C Partner Descriptor
+ * @pd_revision: USB Power Delivery Specification revision (0 = no USB PD)
+ * @vdo: VDO returned by Discover Identity USB PD command
+ * @accessory: Audio, Debug or none.
+ *
+ * Details about a partner that is attached to USB Type-C port.
+ */
+struct typec_partner_desc {
+	u16			pd_revision; /* 0300H = "3.0" */
+	u32			vdo;
+	enum typec_accessory	accessory;
+};
+
+/*
+ * struct typec_capability - USB Type-C Port Capabilities
+ * @role: DFP (Host-only), UFP (Device-only) or DRP (Dual Role)
+ * @revision: USB Type-C Specification release. Binary coded decimal
+ * @pd_revision: USB Power Delivery Specification revision if supported
+ * @prefer_role: Initial role preference
+ * @accessory: Supported Accessory Modes (NULL terminated array)
+ * @try_role: Set data role preference for DRP port
+ * @dr_set: Set Data Role
+ * @pr_set: Set Power Role
+ * @vconn_set: Set VCONN Role
+ * @activate_mode: Enter/exit given Alternate Mode
+ *
+ * Static capabilities of a single USB Type-C port.
+ */
+struct typec_capability {
+	enum typec_port_type	type;
+	u16			revision; /* 0120H = "1.2" */
+	u16			pd_revision; /* 0300H = "3.0" */
+	int			prefer_role;
+	enum typec_accessory	*accessory;
+
+	int		(*try_role)(const struct typec_capability *,
+				    int role);
+
+	int		(*dr_set)(const struct typec_capability *,
+				  enum typec_data_role);
+	int		(*pr_set)(const struct typec_capability *,
+				  enum typec_role);
+	int		(*vconn_set)(const struct typec_capability *,
+				     enum typec_role);
+
+	int		(*activate_mode)(const struct typec_capability *,
+					 int mode, int activate);
+};
+
+/* Specific to try_role(). Indicates the user want's to clear the preference. */
+#define TYPEC_NO_PREFERRED_ROLE	(-1)
+
+struct typec_port *typec_register_port(struct device *parent,
+				       const struct typec_capability *cap);
+void typec_unregister_port(struct typec_port *port);
+
+struct typec_partner *typec_register_partner(struct typec_port *port,
+					     struct typec_partner_desc *desc);
+void typec_unregister_partner(struct typec_partner *partner);
+
+struct typec_cable *typec_register_cable(struct typec_port *port,
+					 struct typec_cable_desc *desc);
+void typec_unregister_cable(struct typec_cable *cable);
+
+struct typec_plug *typec_register_plug(struct typec_cable *cable,
+				       struct typec_plug_desc *desc);
+void typec_unregister_plug(struct typec_plug *plug);
+
+void typec_set_data_role(struct typec_port *port, enum typec_data_role role);
+void typec_set_pwr_role(struct typec_port *port, enum typec_role role);
+void typec_set_vconn_role(struct typec_port *port, enum typec_role role);
+void typec_set_pwr_opmode(struct typec_port *port, enum typec_pwr_opmode mode);
+
+#endif /* __LINUX_USB_TYPEC_H */
-- 
2.10.2

^ permalink raw reply related	[flat|nested] 19+ messages in thread

* Re: [RFC PATCH] usb: USB Type-C connector class
  2016-12-19 14:45                 ` [RFC PATCH] " Heikki Krogerus
@ 2016-12-19 17:22                   ` Guenter Roeck
  2016-12-20 14:23                     ` Heikki Krogerus
  0 siblings, 1 reply; 19+ messages in thread
From: Guenter Roeck @ 2016-12-19 17:22 UTC (permalink / raw)
  To: Heikki Krogerus, Oliver Neukum; +Cc: Greg KH, linux-kernel, linux-usb

On 12/19/2016 06:45 AM, Heikki Krogerus wrote:
> The purpose of USB Type-C connector class is to provide
> unified interface for the user space to get the status and
> basic information about USB Type-C connectors on a system,
> control over data role swapping, and when the port supports
> USB Power Delivery, also control over power role swapping
> and Alternate Modes.
>
> Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> ---
>  Documentation/ABI/testing/sysfs-class-typec |  220 ++++++
>  Documentation/usb/typec.txt                 |  174 +++++
>  MAINTAINERS                                 |    9 +
>  drivers/usb/Kconfig                         |    2 +
>  drivers/usb/Makefile                        |    2 +
>  drivers/usb/typec/Kconfig                   |    7 +
>  drivers/usb/typec/Makefile                  |    1 +
>  drivers/usb/typec/typec.c                   | 1047 +++++++++++++++++++++++++++
>  include/linux/usb/typec.h                   |  212 ++++++
>  9 files changed, 1674 insertions(+)
>  create mode 100644 Documentation/ABI/testing/sysfs-class-typec
>  create mode 100644 Documentation/usb/typec.txt
>  create mode 100644 drivers/usb/typec/Kconfig
>  create mode 100644 drivers/usb/typec/Makefile
>  create mode 100644 drivers/usb/typec/typec.c
>  create mode 100644 include/linux/usb/typec.h
>
>
> Hi,
>
> There was one more change to the ABI: the ports are now named
> "port<id>" instead of "usbc<id>". Let me know if that is OK!
>
> The series is still being reviewed internally, but I'm sending this
> already here so you guys can take a look.
>
> The API now expects the drivers to register every part separately,
> including the alternate modes for the port itself. Structures for the
> partner/cable/plug/altmode are now protected, and the drivers need to
> register those with separate descriptor structures and get a handle to
> struct typec_partner/cable/plug/altmode if the registration is
> successful, or just NULL in case of failure as Greg proposed.
>
> As the drivers need to register the partners and the cables separately
> in any case, the drivers do not declare connections with the class
> anymore. If the default role changes during initial negotiation with a
> partner, the drivers need to notify the class before registering the
> partner or cable.
>

Can you also publish the Whiskey Cove driver ? That might help figuring out
the necessary driver changes.

Thanks,
Guenter


>
> Thanks,
>
>
> diff --git a/Documentation/ABI/testing/sysfs-class-typec b/Documentation/ABI/testing/sysfs-class-typec
> new file mode 100644
> index 0000000..663709a
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-class-typec
> @@ -0,0 +1,220 @@
> +USB Type-C port devices (eg. /sys/class/typec/port0/)
> +
> +What:		/sys/class/typec/<port>/current_data_role
> +Date:		February 2017
> +Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> +		The current USB data role the port is operating in. 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.
> +
> +		Valid values:
> +		- host
> +		- device
> +
> +What:		/sys/class/typec/<port>/current_power_role
> +Date:		February 2017
> +Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> +		The current power role of the port. 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.
> +
> +		Valid values:
> +		- source
> +		- sink
> +
> +What:		/sys/class/typec/<port>/vconn_source
> +Date:		February 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 are:
> +		- 0 when the port is not the VCONN Source
> +		- 1 when the port is the VCONN Source
> +
> +What:		/sys/class/typec/<port>/power_operation_mode
> +Date:		February 2017
> +Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> +		Shows the current power operational mode the port is in.
> +
> +		Valid values:
> +		- USB - Normal power levels defined in USB specifications
> +		- USB Type-C 1.5A - Higher 1.5A current defined in USB Type-C
> +				    specification.
> +		- USB Type-C 3.0A - Higher 3A current defined in USB Type-C
> +				    specification.
> +                - USB Power Delivery - The voltages and currents defined in USB
> +				       Power Delivery specification
> +
> +What:		/sys/class/typec/<port>/preferred_role
> +Date:		February 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 there is no preferred role.
> +
> +		Valid values:
> +		- source
> +		- sink
> +		- none - to remove preference
> +
> +What:		/sys/class/typec/<port>/supported_accessory_modes
> +Date:		February 2017
> +Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> +		Lists the Accessory Modes, defined in the USB Type-C
> +		specification, the port supports.
> +
> +What:		/sys/class/typec/<port>/supported_data_roles
> +Date:		February 2017
> +Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> +		Lists the USB data roles the port is capable of supporting.
> +
> +		Valid values:
> +		- device
> +		- host
> +		- device, host (DRD as defined in USB Type-C specification v1.2)
> +
> +What:		/sys/class/typec/<port>/supported_power_roles
> +Date:		February 2017
> +Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> +		Lists the power roles the port is capable of supporting.
> +
> +		Valid values:
> +		- source
> +		- sink
> +		- source, sink (DRP as defined in USB Type-C specification v1.2)
> +
> +What:		/sys/class/typec/<port>/supports_usb_power_delivery
> +Date:		February 2017
> +Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> +		Shows if the port supports USB Power Delivery.
> +		- 1 if USB Power Delivery is supported
> +		- 0 when it's not
> +
> +
> +USB Type-C partner devices (eg. /sys/class/typec/port0-partner/)
> +
> +What:		/sys/class/typec/<port>-partner/accessory_mode
> +Date:		February 2017
> +Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> +		Shows the Accessory Mode name, or "no" when the partner does not
> +		support Accesory Modes. The Accessory Modes are defined in USB
> +		Type-C Specification.
> +
> +What:		/sys/class/typec/<port>-partner/supports_usb_power_delivery
> +Date:		February 2017
> +Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> +		Shows if the partner supports USB Power Delivery:
> +		- 0 when USB Power Delivery is not supported
> +		- 1 when USB Power Delivery is supported
> +
> +
> +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 their alternate modes
> +as described in USB Type-C and USB Power Delivery specifications.
> +
> +What:		/sys/class/typec/<port>-cable/active
> +Date:		February 2017
> +Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> +		Shows if the cable is active or passive.
> +
> +		Valid values:
> +		- 0 when the cable is passive
> +		- 1 when the cable is active
> +
> +What:		/sys/class/typec/<port>-cable/plug_type
> +Date:		February 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 - USB Type-C
> +		- Captive - Non-standard
> +
> +What:		/sys/class/typec/<port>-cable/supports_usb_power_delivery
> +Date:		February 2017
> +Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> +		Shows if the cable supports USB Power Delivery:
> +		- 0 when USB Power Delivery is not supported
> +		- 1 when USB Power Delivery is supported
> +
> +
> +Alternate Mode devices (For example,
> +/sys/class/typec/port0-partner/port0-partner.svid:xxxx/). The ports, partners
> +and cable plugs can have alternate modes.
> +
> +What:		/sys/class/typec/<dev>/<dev>.svid:<svid>/<mode>/active
> +Date:		February 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:
> +		- 0 when the mode is deactive
> +		- 1 when the mode is active
> +
> +What:		/sys/class/typec/<dev>/<dev>.svid:<svid>/<mode>/description
> +Date:		February 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/<dev>/<dev>.svid:<svid>/<mode>/vdo
> +Date:		February 2017
> +Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> +		Shows the VDO in hexadecimal returned from the Discover Modes
> +		command.
> +
> +What:		/sys/class/typec/<port>/<port>.svid:<svid>/<mode>/supported_roles
> +Date:		February 2017
> +Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> +		Shows the roles, source or sink, the mode is supported with.
> +
> +		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.
> diff --git a/Documentation/usb/typec.txt b/Documentation/usb/typec.txt
> new file mode 100644
> index 0000000..e588657
> --- /dev/null
> +++ b/Documentation/usb/typec.txt
> @@ -0,0 +1,174 @@
> +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.
> +
> +
> +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. The
> +partner attached to port "port0" will be named "port0-partner". Full path to the
> +device would be /sys/class/typec/usb0/usb0-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 support Alternate Modes, every Alternate Mode
> +SVID will have their own device describing them. The Alternate Modes will not be
> +attached to the typec class. For the port's "port0" partner, the Alternate Modes
> +would have devices 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/port0.svid:xxxx/mode0/. The
> +requests for entering/exiting a mode happens with "active" attribute file in
> +that group.
> +
> +
> +API
> +---
> +
> +The functions and structures of the class are defined in
> +linux/include/usb/typec.h header.
> +
> +
> +* 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:
> +
> +struct typec_port *typec_register_port(struct device *parent,
> +				       const struct typec_capability *cap);
> +
> +The class will provide handle to struct typec_port on success and NULL on
> +failure. The un-registration of the port happens with the following API:
> +
> +void typec_unregister_port(struct typec_port *port);
> +
> +When registering the ports, the prefer_role member in struct typec_capability
> +deservers 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.
> +
> +struct typec_partner *typec_register_partner(struct typec_port *port,
> +					     struct typec_partner_desc *desc);
> +void typec_unregister_partner(struct typec_partner *partner);
> +
> +The class will provide a handle to struct typec_partner if the registration was
> +successful, or NULL.
> +
> +
> +* 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 maximum two plugs the cable has with the class. Details about the
> +cable need to be described in struct typec_cable_desc and about the 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:
> +
> +struct typec_cable *typec_register_cable(struct typec_port *port,
> +					 struct typec_cable_desc *desc);
> +void typec_unregister_cable(struct typec_cable *cable);
> +
> +struct typec_plug *typec_register_plug(struct typec_cable *cable,
> +				       struct typec_plug_desc *desc);
> +void typec_unregister_plug(struct typec_plug *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.
> +
> +
> +* Notifications
> +
> +When the partner end 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:
> +
> +void typec_set_data_role(struct typec_port *, enum typec_data_role);
> +void typec_set_pwr_role(struct typec_port *, enum typec_role);
> +void typec_set_vconn_role(struct typec_port *, enum typec_role);
> +void typec_set_pwr_opmode(struct typec_port *, enum typec_pwr_opmode);
> +
> +
> +* Alternate Modes
> +
> +USB Type-C Ports, Partners and Cable Plugs may support Alternate Modes with USB
> +Type-C. Each Alternate Mode will have SVID, which is either a Standard ID given
> +by USB-IF or vendor ID. Each SVID will support 1 - 7 modes. The class provides
> +struct typec_mode_desc for describing individual mode of a SVID, and struct
> +typec_altmode_desc which is a container of all the modes of a SVID that a
> +port, partner or cable plug supports.
> +
> +Ports that support Alternate Modes need to register each SVID they support with
> +the following API:
> +
> +struct typec_altmode
> +*typec_port_register_altmode(struct typec_port *port,
> +			     struct typec_altmode_desc *desc);
> +
> +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
> +with the following API.
> +
> +API for the partners:
> +
> +struct typec_altmode
> +*typec_partner_register_altmode(struct typec_partner *partner,
> +				struct typec_altmode_desc *desc);
> +
> +API for the Cable Plugs:
> +
> +struct typec_altmode
> +*typec_plug_register_altmode(struct typec_plug *plug,
> +			     struct typec_altmode_desc *desc);
> +
> +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:
> +
> +void typec_unregister_altmode(struct typec_altmode *altmode);
> +
> +If a partner or cable plug enters or exits a mode, the port driver needs to
> +notify the class with the following API:
> +
> +void typec_altmode_update_active(struct typec_altmode *alt, int mode,
> +				 bool active);
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 10581bd..bcc88cd 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -12873,6 +12873,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.txt
> +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
> diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
> index fbe493d..89c322e 100644
> --- a/drivers/usb/Kconfig
> +++ b/drivers/usb/Kconfig
> @@ -152,6 +152,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
> diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
> index 7791af6..c7f4098 100644
> --- a/drivers/usb/Makefile
> +++ b/drivers/usb/Makefile
> @@ -62,3 +62,5 @@ obj-$(CONFIG_USB_GADGET)	+= gadget/
>  obj-$(CONFIG_USB_COMMON)	+= common/
>
>  obj-$(CONFIG_USBIP_CORE)	+= usbip/
> +
> +obj-$(CONFIG_TYPEC)		+= typec/
> diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
> new file mode 100644
> index 0000000..17792f9
> --- /dev/null
> +++ b/drivers/usb/typec/Kconfig
> @@ -0,0 +1,7 @@
> +
> +menu "USB Power Delivery and Type-C drivers"
> +
> +config TYPEC
> +	tristate
> +
> +endmenu
> diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
> new file mode 100644
> index 0000000..1012a8b
> --- /dev/null
> +++ b/drivers/usb/typec/Makefile
> @@ -0,0 +1 @@
> +obj-$(CONFIG_TYPEC)		+= typec.o
> diff --git a/drivers/usb/typec/typec.c b/drivers/usb/typec/typec.c
> new file mode 100644
> index 0000000..0e9dd2e
> --- /dev/null
> +++ b/drivers/usb/typec/typec.c
> @@ -0,0 +1,1047 @@
> +/*
> + * USB Type-C Connector Class
> + *
> + * Copyright (C) 2017, Intel Corporation
> + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.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 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/device.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/usb/typec.h>
> +
> +/* XXX: Once we have a header for USB Power Delivery, this belongs there */
> +#define ALTMODE_MAX_N_MODES	7
> +
> +struct typec_mode {
> +	int			index;
> +	u32			vdo;
> +	char			*desc;
> +	enum typec_port_type	roles;
> +
> +	struct typec_altmode	*alt_mode;
> +
> +	unsigned int		active:1;
> +
> +	char			group_name[6];
> +	struct attribute_group	group;
> +	struct attribute	*attrs[5];
> +	struct device_attribute vdo_attr;
> +	struct device_attribute desc_attr;
> +	struct device_attribute active_attr;
> +	struct device_attribute roles_attr;
> +};
> +
> +struct typec_altmode {
> +	struct device			dev;
> +	u16				svid;
> +	int				n_modes;
> +	struct typec_mode		modes[ALTMODE_MAX_N_MODES];
> +	const struct attribute_group	*mode_groups[ALTMODE_MAX_N_MODES];
> +};
> +
> +struct typec_plug {
> +	struct device			dev;
> +	enum typec_plug_index		index;
> +};
> +
> +struct typec_cable {
> +	struct device			dev;
> +	u16				pd_revision;
> +	enum typec_plug_type		type;
> +	u32				vdo;
> +	unsigned int			active:1;
> +};
> +
> +struct typec_partner {
> +	struct device			dev;
> +	u16				pd_revision;
> +	u32				vdo;
> +	enum typec_accessory		accessory;
> +};
> +
> +struct typec_port {
> +	unsigned int			id;
> +	struct device			dev;
> +
> +	int				prefer_role;
> +	enum typec_data_role		data_role;
> +	enum typec_role			pwr_role;
> +	enum typec_role			vconn_role;
> +	enum typec_pwr_opmode		pwr_opmode;
> +
> +	const struct typec_capability	*cap;
> +};
> +
> +#define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev)
> +#define to_typec_plug(_dev_) container_of(_dev_, struct typec_plug, dev)
> +#define to_typec_cable(_dev_) container_of(_dev_, struct typec_cable, dev)
> +#define to_typec_partner(_dev_) container_of(_dev_, struct typec_partner, dev)
> +#define to_altmode(_dev_) container_of(_dev_, struct typec_altmode, dev)
> +
> +static struct device_type typec_partner_dev_type;
> +static struct device_type typec_cable_dev_type;
> +static struct device_type typec_plug_dev_type;
> +static struct device_type typec_port_dev_type;
> +
> +#define is_typec_partner(_dev_) (_dev_->type == &typec_partner_dev_type)
> +#define is_typec_cable(_dev_) (_dev_->type == &typec_cable_dev_type)
> +#define is_typec_plug(_dev_) (_dev_->type == &typec_plug_dev_type)
> +#define is_typec_port(_dev_) (_dev_->type == &typec_port_dev_type)
> +
> +static DEFINE_IDA(typec_index_ida);
> +static struct class *typec_class;
> +
> +/* Common attributes */
> +
> +static const char * const typec_accessory_modes[] = {
> +	[TYPEC_ACCESSORY_NONE]	= "",
> +	[TYPEC_ACCESSORY_AUDIO]	= "Audio Adapter Accessory Mode",
> +	[TYPEC_ACCESSORY_DEBUG]	= "Debug Accessory Mode",
> +};
> +
> +static ssize_t usb_power_delivery_revision_show(struct device *dev,
> +						struct device_attribute *attr,
> +						char *buf)
> +{
> +	u16 rev = 0;
> +
> +	if (is_typec_partner(dev)) {
> +		struct typec_partner *p = to_typec_partner(dev);
> +
> +		rev = p->pd_revision;
> +	} else if (is_typec_cable(dev)) {
> +		struct typec_cable *p = to_typec_cable(dev);
> +
> +		rev = p->pd_revision;
> +	} else if (is_typec_port(dev)) {
> +		struct typec_port *p = to_typec_port(dev);
> +
> +		rev = p->cap->pd_revision;
> +	}
> +
> +	return sprintf(buf, "%d\n", (rev >> 8) & 0xff);
> +}
> +static DEVICE_ATTR_RO(usb_power_delivery_revision);
> +
> +static ssize_t
> +vdo_show(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> +	u32 vdo = 0;
> +
> +	if (is_typec_partner(dev)) {
> +		struct typec_partner *p = to_typec_partner(dev);
> +
> +		vdo = p->vdo;
> +	} else if (is_typec_cable(dev)) {
> +		struct typec_cable *p = to_typec_cable(dev);
> +
> +		vdo = p->vdo;
> +	}
> +
> +	return sprintf(buf, "0x%08x\n", vdo);
> +}
> +static DEVICE_ATTR_RO(vdo);
> +
> +/* ------------------------------------------------------------------------- */
> +/* Alternate Modes */
> +
> +/*
> + * typec_altmode_update_active - Notify about Enter/Exit mode
> + * @alt: Handle to the Alternate Mode
> + * @mode: Mode index
> + * @active: True when the mode has been enterred
> + */
> +void typec_altmode_update_active(struct typec_altmode *alt, int mode,
> +				 bool active)
> +{
> +	struct typec_mode *m = &alt->modes[mode];
> +	char dir[6];
> +
> +	if (m->active == active)
> +		return;
> +
> +	m->active = active;
> +	snprintf(dir, 6, "mode%d", mode);
> +	sysfs_notify(&alt->dev.kobj, dir, "active");
> +	kobject_uevent(&alt->dev.kobj, KOBJ_CHANGE);
> +}
> +EXPORT_SYMBOL_GPL(typec_altmode_update_active);
> +
> +/*
> + * typec_altmode2port - Alternate Mode to USB Type-C port
> + * @alt: The Alternate Mode
> + *
> + * Returns the port that the cable plug or partner with @alt is connected to.
> + */
> +struct typec_port *typec_altmode2port(struct typec_altmode *alt)
> +{
> +	if (is_typec_plug(alt->dev.parent))
> +		return to_typec_port(alt->dev.parent->parent->parent);
> +	if (is_typec_partner(alt->dev.parent))
> +		return to_typec_port(alt->dev.parent->parent);
> +	if (is_typec_port(alt->dev.parent))
> +		return to_typec_port(alt->dev.parent);
> +
> +	return NULL;
> +}
> +EXPORT_SYMBOL_GPL(typec_altmode2port);
> +
> +static void typec_altmode_release(struct device *dev)
> +{
> +	struct typec_altmode *alt = to_altmode(dev);
> +	int i;
> +
> +	for (i = 0; i < alt->n_modes; i++)
> +		kfree(alt->modes[i].desc);
> +	kfree(alt);
> +}
> +
> +static ssize_t
> +typec_altmode_vdo_show(struct device *dev, struct device_attribute *attr,
> +		       char *buf)
> +{
> +	struct typec_mode *mode = container_of(attr, struct typec_mode,
> +					       vdo_attr);
> +
> +	return sprintf(buf, "0x%08x\n", mode->vdo);
> +}
> +
> +static ssize_t
> +typec_altmode_desc_show(struct device *dev, struct device_attribute *attr,
> +			char *buf)
> +{
> +	struct typec_mode *mode = container_of(attr, struct typec_mode,
> +					       desc_attr);
> +
> +	return sprintf(buf, "%s\n", mode->desc ? mode->desc : "");
> +}
> +
> +static ssize_t
> +typec_altmode_active_show(struct device *dev, struct device_attribute *attr,
> +			  char *buf)
> +{
> +	struct typec_mode *mode = container_of(attr, struct typec_mode,
> +					       active_attr);
> +
> +	return sprintf(buf, "%d\n", mode->active);
> +}
> +
> +static ssize_t
> +typec_altmode_active_store(struct device *dev, struct device_attribute *attr,
> +			   const char *buf, size_t size)
> +{
> +	struct typec_mode *mode = container_of(attr, struct typec_mode,
> +					       active_attr);
> +	struct typec_port *port = typec_altmode2port(mode->alt_mode);
> +	bool activate;
> +	int ret;
> +
> +	if (!port->cap->activate_mode)
> +		return -EOPNOTSUPP;
> +
> +	ret = kstrtobool(buf, &activate);
> +	if (ret)
> +		return ret;
> +
> +	ret = port->cap->activate_mode(port->cap, mode->index, activate);
> +	if (ret)
> +		return ret;
> +
> +	return size;
> +}
> +
> +static ssize_t
> +typec_altmode_roles_show(struct device *dev, struct device_attribute *attr,
> +			 char *buf)
> +{
> +	struct typec_mode *mode = container_of(attr, struct typec_mode,
> +					       roles_attr);
> +	ssize_t ret;
> +
> +	switch (mode->roles) {
> +	case TYPEC_PORT_DFP:
> +		ret =  sprintf(buf, "source\n");
> +		break;
> +	case TYPEC_PORT_UFP:
> +		ret = sprintf(buf, "sink\n");
> +		break;
> +	case TYPEC_PORT_DRP:
> +	default:
> +		ret = sprintf(buf, "source\nsink\n");
> +		break;
> +	}
> +	return ret;
> +}
> +
> +static inline void typec_init_modes(struct typec_altmode *alt,
> +				    struct typec_mode_desc *desc, bool is_port)
> +{
> +	int i;
> +
> +	for (i = 0; i < alt->n_modes; i++, desc++) {
> +		struct typec_mode *mode = &alt->modes[i];
> +
> +		/* Not considering the human readable description critical */
> +		mode->desc = kstrdup(desc->desc, GFP_KERNEL);
> +		if (desc->desc && !mode->desc)
> +			dev_err(&alt->dev, "failed to copy mode%d desc\n", i);
> +
> +		mode->alt_mode = alt;
> +		mode->vdo = desc->vdo;
> +		mode->roles = desc->roles;
> +		mode->index = desc->index;
> +		sprintf(mode->group_name, "mode%d", desc->index);
> +
> +		sysfs_attr_init(&mode->vdo_attr.attr);
> +		mode->vdo_attr.attr.name = "vdo";
> +		mode->vdo_attr.attr.mode = 0444;
> +		mode->vdo_attr.show = typec_altmode_vdo_show;
> +
> +		sysfs_attr_init(&mode->desc_attr.attr);
> +		mode->desc_attr.attr.name = "description";
> +		mode->desc_attr.attr.mode = 0444;
> +		mode->desc_attr.show = typec_altmode_desc_show;
> +
> +		sysfs_attr_init(&mode->active_attr.attr);
> +		mode->active_attr.attr.name = "active";
> +		mode->active_attr.attr.mode = 0644;
> +		mode->active_attr.show = typec_altmode_active_show;
> +		mode->active_attr.store = typec_altmode_active_store;
> +
> +		mode->attrs[0] = &mode->vdo_attr.attr;
> +		mode->attrs[1] = &mode->desc_attr.attr;
> +		mode->attrs[2] = &mode->active_attr.attr;
> +
> +		/* With ports, list the roles that the mode is supported with */
> +		if (is_port) {
> +			sysfs_attr_init(&mode->roles_attr.attr);
> +			mode->roles_attr.attr.name = "supported_roles";
> +			mode->roles_attr.attr.mode = 0444;
> +			mode->roles_attr.show = typec_altmode_roles_show;
> +
> +			mode->attrs[3] = &mode->roles_attr.attr;
> +		}
> +
> +		mode->group.attrs = mode->attrs;
> +		mode->group.name = mode->group_name;
> +
> +		alt->mode_groups[i] = &mode->group;
> +	}
> +}
> +
> +static struct typec_altmode
> +*typec_register_altmode(struct device *parent, struct typec_altmode_desc *desc)
> +{
> +	struct typec_altmode *alt;
> +	int ret;
> +
> +	alt = kzalloc(sizeof(*alt), GFP_KERNEL);
> +	if (!alt)
> +		return NULL;
> +
> +	alt->svid = desc->svid;
> +	alt->n_modes = desc->n_modes;
> +	typec_init_modes(alt, desc->modes, is_typec_port(parent));
> +
> +	alt->dev.parent = parent;
> +	alt->dev.groups = alt->mode_groups;
> +	alt->dev.release = typec_altmode_release;
> +	dev_set_name(&alt->dev, "%s.svid:%04x", dev_name(parent), alt->svid);
> +
> +	ret = device_register(&alt->dev);
> +	if (ret) {
> +		int i;
> +
> +		dev_err(parent, "failed to register alternate mode (%d)\n",
> +			ret);
> +
> +		put_device(&alt->dev);
> +
> +		for (i = 0; i < alt->n_modes; i++)
> +			kfree(alt->modes[i].desc);
> +		kfree(alt);
> +		return NULL;
> +	}
> +
> +	return alt;
> +}
> +
> +void typec_unregister_altmode(struct typec_altmode *alt)
> +{
> +	if (alt)
> +		device_unregister(&alt->dev);
> +}
> +EXPORT_SYMBOL_GPL(typec_unregister_altmode);
> +
> +/* ------------------------------------------------------------------------- */
> +/* Type-C Partners */
> +
> +static ssize_t accessory_mode_show(struct device *dev,
> +				   struct device_attribute *attr,
> +				   char *buf)
> +{
> +	struct typec_partner *p = to_typec_partner(dev);
> +
> +	return sprintf(buf, "%s\n", typec_accessory_modes[p->accessory]);
> +}
> +static DEVICE_ATTR_RO(accessory_mode);
> +
> +static struct attribute *typec_partner_attrs[] = {
> +	&dev_attr_vdo.attr,
> +	&dev_attr_accessory_mode.attr,
> +	&dev_attr_usb_power_delivery_revision.attr,
> +	NULL
> +};
> +ATTRIBUTE_GROUPS(typec_partner);
> +
> +static void typec_partner_release(struct device *dev)
> +{
> +	struct typec_partner *partner = to_typec_partner(dev);
> +
> +	kfree(partner);
> +}
> +
> +static struct device_type typec_partner_dev_type = {
> +	.name = "typec_partner_device",
> +	.groups = typec_partner_groups,
> +	.release = typec_partner_release,
> +};
> +
> +struct typec_altmode
> +*typec_partner_register_altmode(struct typec_partner *partner,
> +				struct typec_altmode_desc *desc)
> +{
> +	return typec_register_altmode(&partner->dev, desc);
> +}
> +EXPORT_SYMBOL_GPL(typec_partner_register_altmode);
> +
> +struct typec_partner *typec_register_partner(struct typec_port *port,
> +					     struct typec_partner_desc *desc)
> +{
> +	struct typec_partner *partner = NULL;
> +	int ret;
> +
> +	partner = kzalloc(sizeof(*partner), GFP_KERNEL);
> +	if (!partner)
> +		return NULL;
> +
> +	partner->vdo = desc->vdo;
> +	partner->accessory = desc->accessory;
> +	partner->pd_revision = desc->pd_revision;
> +
> +	partner->dev.class = typec_class;
> +	partner->dev.parent = &port->dev;
> +	partner->dev.type = &typec_partner_dev_type;
> +	dev_set_name(&partner->dev, "%s-partner", dev_name(&port->dev));
> +
> +	ret = device_register(&partner->dev);
> +	if (ret) {
> +		dev_err(&port->dev, "failed to register partner (%d)\n", ret);
> +		put_device(&partner->dev);
> +		kfree(partner);
> +		return NULL;
> +	}
> +
> +	return partner;
> +}
> +EXPORT_SYMBOL_GPL(typec_register_partner);
> +
> +void typec_unregister_partner(struct typec_partner *partner)
> +{
> +	if (partner)
> +		device_unregister(&partner->dev);
> +}
> +EXPORT_SYMBOL_GPL(typec_unregister_partner);
> +
> +/* ------------------------------------------------------------------------- */
> +/* Type-C Cable Plugs */
> +
> +static void typec_plug_release(struct device *dev)
> +{
> +	struct typec_plug *plug = to_typec_plug(dev);
> +
> +	kfree(plug);
> +}
> +
> +static struct device_type typec_plug_dev_type = {
> +	.name = "typec_plug_device",
> +	.release = typec_plug_release,
> +};
> +
> +struct typec_altmode
> +*typec_plug_register_altmode(struct typec_plug *plug,
> +			     struct typec_altmode_desc *desc)
> +{
> +	return typec_register_altmode(&plug->dev, desc);
> +}
> +EXPORT_SYMBOL_GPL(typec_plug_register_altmode);
> +
> +struct typec_plug *typec_register_plug(struct typec_cable *cable,
> +				       struct typec_plug_desc *desc)
> +{
> +	struct typec_plug *plug = NULL;
> +	char name[8];
> +	int ret;
> +
> +	plug = kzalloc(sizeof(*plug), GFP_KERNEL);
> +	if (!plug)
> +		return NULL;
> +
> +	sprintf(name, "plug%d", desc->index);
> +
> +	plug->index = desc->index;
> +	plug->dev.class = typec_class;
> +	plug->dev.parent = &cable->dev;
> +	plug->dev.type = &typec_plug_dev_type;
> +	dev_set_name(&plug->dev, "%s-%s", dev_name(cable->dev.parent), name);
> +
> +	ret = device_register(&plug->dev);
> +	if (ret) {
> +		dev_err(&cable->dev, "failed to register plug (%d)\n", ret);
> +		put_device(&plug->dev);
> +		kfree(plug);
> +		return NULL;
> +	}
> +
> +	return plug;
> +}
> +EXPORT_SYMBOL_GPL(typec_register_plug);
> +
> +void typec_unregister_plug(struct typec_plug *plug)
> +{
> +	if (plug)
> +		device_unregister(&plug->dev);
> +}
> +EXPORT_SYMBOL_GPL(typec_unregister_plug);
> +
> +/* Type-C Cables */
> +
> +static ssize_t
> +active_show(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> +	struct typec_cable *cable = to_typec_cable(dev);
> +
> +	return sprintf(buf, "%d\n", cable->active);
> +}
> +static DEVICE_ATTR_RO(active);
> +
> +static const char * const typec_plug_types[] = {
> +	[USB_PLUG_NONE]		= "unknown",
> +	[USB_PLUG_TYPE_A]	= "Type-A",
> +	[USB_PLUG_TYPE_B]	= "Type-B",
> +	[USB_PLUG_TYPE_C]	= "Type-C",
> +	[USB_PLUG_CAPTIVE]	= "Captive",
> +};
> +
> +static ssize_t plug_type_show(struct device *dev,
> +			      struct device_attribute *attr, char *buf)
> +{
> +	struct typec_cable *cable = to_typec_cable(dev);
> +
> +	return sprintf(buf, "%s\n", typec_plug_types[cable->type]);
> +}
> +static DEVICE_ATTR_RO(plug_type);
> +
> +static struct attribute *typec_cable_attrs[] = {
> +	&dev_attr_active.attr,
> +	&dev_attr_plug_type.attr,
> +	&dev_attr_usb_power_delivery_revision.attr,
> +	NULL
> +};
> +ATTRIBUTE_GROUPS(typec_cable);
> +
> +static void typec_cable_release(struct device *dev)
> +{
> +	struct typec_cable *cable = to_typec_cable(dev);
> +
> +	kfree(cable);
> +}
> +
> +static struct device_type typec_cable_dev_type = {
> +	.name = "typec_cable_device",
> +	.groups = typec_cable_groups,
> +	.release = typec_cable_release,
> +};
> +
> +struct typec_cable *typec_register_cable(struct typec_port *port,
> +					 struct typec_cable_desc *desc)
> +{
> +	struct typec_cable *cable = NULL;
> +	int ret;
> +
> +	cable = kzalloc(sizeof(*cable), GFP_KERNEL);
> +	if (!cable)
> +		return NULL;
> +
> +	cable->type = desc->type;
> +	cable->vdo = desc->vdo;
> +	cable->active = desc->active;
> +	cable->pd_revision = desc->pd_revision;
> +
> +	cable->dev.class = typec_class;
> +	cable->dev.parent = &port->dev;
> +	cable->dev.type = &typec_cable_dev_type;
> +	dev_set_name(&cable->dev, "%s-cable", dev_name(&port->dev));
> +
> +	ret = device_register(&cable->dev);
> +	if (ret) {
> +		dev_err(&port->dev, "failed to register cable (%d)\n", ret);
> +		put_device(&cable->dev);
> +		kfree(cable);
> +		return NULL;
> +	}
> +
> +	return cable;
> +}
> +EXPORT_SYMBOL_GPL(typec_register_cable);
> +
> +void typec_unregister_cable(struct typec_cable *cable)
> +{
> +	if (cable)
> +		device_unregister(&cable->dev);
> +}
> +EXPORT_SYMBOL_GPL(typec_unregister_cable);
> +
> +/* ------------------------------------------------------------------------- */
> +/* USB Type-C ports */
> +
> +/* --------------------------------------- */
> +/* Driver callbacks to report role updates */
> +
> +void typec_set_data_role(struct typec_port *port, enum typec_data_role role)
> +{
> +	if (port->data_role == role)
> +		return;
> +
> +	port->data_role = role;
> +	sysfs_notify(&port->dev.kobj, NULL, "current_data_role");
> +	kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
> +}
> +EXPORT_SYMBOL_GPL(typec_set_data_role);
> +
> +void typec_set_pwr_role(struct typec_port *port, enum typec_role role)
> +{
> +	if (port->pwr_role == role)
> +		return;
> +
> +	port->pwr_role = role;
> +	sysfs_notify(&port->dev.kobj, NULL, "current_power_role");
> +	kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
> +}
> +EXPORT_SYMBOL_GPL(typec_set_pwr_role);
> +
> +void typec_set_vconn_role(struct typec_port *port, enum typec_role role)
> +{
> +	if (port->vconn_role == role)
> +		return;
> +
> +	port->vconn_role = role;
> +	sysfs_notify(&port->dev.kobj, NULL, "current_vconn_role");
> +	kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
> +}
> +EXPORT_SYMBOL_GPL(typec_set_vconn_role);
> +
> +void typec_set_pwr_opmode(struct typec_port *port,
> +			  enum typec_pwr_opmode opmode)
> +{
> +	if (port->pwr_opmode == opmode)
> +		return;
> +
> +	port->pwr_opmode = opmode;
> +	sysfs_notify(&port->dev.kobj, NULL, "power_operation_mode");
> +	kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
> +}
> +EXPORT_SYMBOL_GPL(typec_set_pwr_opmode);
> +
> +/* --------------------------------------- */
> +
> +static const char * const typec_roles[] = {
> +	[TYPEC_SINK]	= "sink",
> +	[TYPEC_SOURCE]	= "source",
> +};
> +
> +static const char * const typec_data_roles[] = {
> +	[TYPEC_DEVICE]	= "device",
> +	[TYPEC_HOST]	= "host",
> +};
> +
> +static ssize_t
> +preferred_role_store(struct device *dev, struct device_attribute *attr,
> +		     const char *buf, size_t size)
> +{
> +	struct typec_port *port = to_typec_port(dev);
> +	int role;
> +	int ret;
> +
> +	if (port->cap->type != TYPEC_PORT_DRP) {
> +		dev_dbg(dev, "Preferred role only supported with DRP ports\n");
> +		return -EOPNOTSUPP;
> +	}
> +
> +	if (!port->cap->try_role) {
> +		dev_dbg(dev, "Setting preferred role not supported\n");
> +		return -EOPNOTSUPP;
> +	}
> +
> +	role = sysfs_match_string(typec_roles, buf);
> +	if (role < 0) {
> +		if (sysfs_streq(buf, "none"))
> +			role = TYPEC_NO_PREFERRED_ROLE;
> +		else
> +			return -EINVAL;
> +	}
> +
> +	ret = port->cap->try_role(port->cap, role);
> +	if (ret)
> +		return ret;
> +
> +	port->prefer_role = role;
> +	return size;
> +}
> +
> +static ssize_t
> +preferred_role_show(struct device *dev, struct device_attribute *attr,
> +		    char *buf)
> +{
> +	struct typec_port *port = to_typec_port(dev);
> +
> +	if (port->cap->type != TYPEC_PORT_DRP)
> +		return 0;
> +
> +	if (port->prefer_role < 0)
> +		return 0;
> +
> +	return sprintf(buf, "%s\n", typec_roles[port->prefer_role]);
> +}
> +static DEVICE_ATTR_RW(preferred_role);
> +
> +static ssize_t
> +current_data_role_store(struct device *dev, struct device_attribute *attr,
> +			const char *buf, size_t size)
> +{
> +	struct typec_port *port = to_typec_port(dev);
> +	int ret;
> +
> +	if (port->cap->type != TYPEC_PORT_DRP) {
> +		dev_dbg(dev, "data role swap only supported with DRP ports\n");
> +		return -EOPNOTSUPP;
> +	}
> +
> +	if (!port->cap->dr_set) {
> +		dev_dbg(dev, "data role swapping not supported\n");
> +		return -EOPNOTSUPP;
> +	}
> +
> +	ret = sysfs_match_string(typec_data_roles, buf);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = port->cap->dr_set(port->cap, ret);
> +	if (ret)
> +		return ret;
> +
> +	return size;
> +}
> +
> +static ssize_t
> +current_data_role_show(struct device *dev, struct device_attribute *attr,
> +		       char *buf)
> +{
> +	struct typec_port *port = to_typec_port(dev);
> +
> +	return sprintf(buf, "%s\n", typec_data_roles[port->data_role]);
> +}
> +static DEVICE_ATTR_RW(current_data_role);
> +
> +static ssize_t supported_data_roles_show(struct device *dev,
> +					 struct device_attribute *attr,
> +					 char *buf)
> +{
> +	struct typec_port *port = to_typec_port(dev);
> +
> +	if (port->cap->type == TYPEC_PORT_DRP)
> +		return sprintf(buf, "host\ndevice\n");
> +
> +	return sprintf(buf, "%s\n", typec_data_roles[port->data_role]);
> +}
> +static DEVICE_ATTR_RO(supported_data_roles);
> +
> +static ssize_t current_power_role_store(struct device *dev,
> +					struct device_attribute *attr,
> +					const char *buf, size_t size)
> +{
> +	struct typec_port *port = to_typec_port(dev);
> +	int ret = size;
> +
> +	if (!port->cap->pd_revision) {
> +		dev_dbg(dev, "power role swap only supported with USB PD\n");
> +		return -EOPNOTSUPP;
> +	}
> +
> +	if (!port->cap->pr_set) {
> +		dev_dbg(dev, "power role swapping not supported\n");
> +		return -EOPNOTSUPP;
> +	}
> +
> +	if (port->pwr_opmode != TYPEC_PWR_MODE_PD) {
> +		dev_dbg(dev, "partner unable to swap power role\n");
> +		return -EIO;
> +	}
> +
> +	ret = sysfs_match_string(typec_roles, buf);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = port->cap->pr_set(port->cap, ret);
> +	if (ret)
> +		return ret;
> +
> +	return size;
> +}
> +
> +static ssize_t current_power_role_show(struct device *dev,
> +				       struct device_attribute *attr, char *buf)
> +{
> +	struct typec_port *port = to_typec_port(dev);
> +
> +	return sprintf(buf, "%s\n", typec_roles[port->pwr_role]);
> +}
> +static DEVICE_ATTR_RW(current_power_role);
> +
> +static ssize_t supported_power_roles_show(struct device *dev,
> +					  struct device_attribute *attr,
> +					  char *buf)
> +{
> +	struct typec_port *port = to_typec_port(dev);
> +
> +	if (port->cap->pd_revision || port->cap->type == TYPEC_PORT_DRP)
> +		return sprintf(buf, "source\nsink\n");
> +
> +	return sprintf(buf, "%s\n", typec_roles[port->pwr_role]);
> +}
> +static DEVICE_ATTR_RO(supported_power_roles);
> +
> +static const char * const typec_pwr_opmodes[] = {
> +	[TYPEC_PWR_MODE_USB]	= "USB",
> +	[TYPEC_PWR_MODE_1_5A]	= "USB Type-C 1.5A",
> +	[TYPEC_PWR_MODE_3_0A]	= "USB Type-C 3.0A",
> +	[TYPEC_PWR_MODE_PD]	= "USB Power Delivery",
> +};
> +
> +static ssize_t power_operation_mode_show(struct device *dev,
> +					 struct device_attribute *attr,
> +					 char *buf)
> +{
> +	struct typec_port *port = to_typec_port(dev);
> +
> +	return sprintf(buf, "%s\n", typec_pwr_opmodes[port->pwr_opmode]);
> +}
> +static DEVICE_ATTR_RO(power_operation_mode);
> +
> +static ssize_t vconn_source_store(struct device *dev,
> +				  struct device_attribute *attr,
> +				  const char *buf, size_t size)
> +{
> +	struct typec_port *port = to_typec_port(dev);
> +	enum typec_role role;
> +	int ret;
> +
> +	if (!port->cap->pd_revision) {
> +		dev_dbg(dev, "vconn swap only supported with USB PD\n");
> +		return -EOPNOTSUPP;
> +	}
> +
> +	if (!port->cap->vconn_set) {
> +		dev_dbg(dev, "vconn swapping not supported\n");
> +		return -EOPNOTSUPP;
> +	}
> +
> +	if (sysfs_streq(buf, "1"))
> +		role = TYPEC_SOURCE;
> +	else if (sysfs_streq(buf, "0"))
> +		role = TYPEC_SINK;
> +	else
> +		return -EINVAL;
> +
> +	ret = port->cap->vconn_set(port->cap, role);
> +	if (ret)
> +		return ret;
> +
> +	return size;
> +}
> +
> +static ssize_t vconn_source_show(struct device *dev,
> +				 struct device_attribute *attr, char *buf)
> +{
> +	struct typec_port *port = to_typec_port(dev);
> +
> +	return sprintf(buf, "%d\n", port->vconn_role == TYPEC_SOURCE ? 1 : 0);
> +}
> +static DEVICE_ATTR_RW(vconn_source);
> +
> +static ssize_t supported_accessory_modes_show(struct device *dev,
> +					      struct device_attribute *attr,
> +					      char *buf)
> +{
> +	struct typec_port *port = to_typec_port(dev);
> +	ssize_t ret = 0;
> +	int i;
> +
> +	if (!port->cap->accessory)
> +		return 0;
> +
> +	for (i = 0; port->cap->accessory[i]; i++)
> +		ret += sprintf(buf + ret, "%s\n",
> +			       typec_accessory_modes[port->cap->accessory[i]]);
> +	return ret;
> +}
> +static DEVICE_ATTR_RO(supported_accessory_modes);
> +
> +static ssize_t usb_typec_revision_show(struct device *dev,
> +				       struct device_attribute *attr,
> +				       char *buf)
> +{
> +	struct typec_port *port = to_typec_port(dev);
> +	u16 rev = port->cap->revision;
> +
> +	return sprintf(buf, "%d.%d\n", (rev >> 8) & 0xff, rev >> 4 & 0xf);
> +}
> +static DEVICE_ATTR_RO(usb_typec_revision);
> +
> +static struct attribute *typec_attrs[] = {
> +	&dev_attr_current_power_role.attr,
> +	&dev_attr_current_data_role.attr,
> +	&dev_attr_power_operation_mode.attr,
> +	&dev_attr_preferred_role.attr,
> +	&dev_attr_supported_accessory_modes.attr,
> +	&dev_attr_supported_data_roles.attr,
> +	&dev_attr_supported_power_roles.attr,
> +	&dev_attr_usb_power_delivery_revision.attr,
> +	&dev_attr_usb_typec_revision.attr,
> +	&dev_attr_vconn_source.attr,
> +	NULL,
> +};
> +ATTRIBUTE_GROUPS(typec);
> +
> +static int typec_uevent(struct device *dev, struct kobj_uevent_env *env)
> +{
> +	int ret;
> +
> +	ret = add_uevent_var(env, "TYPEC_PORT=%s", dev_name(dev));
> +	if (ret)
> +		dev_err(dev, "failed to add uevent TYPEC_PORT\n");
> +
> +	return ret;
> +}
> +
> +static void typec_release(struct device *dev)
> +{
> +	struct typec_port *port = to_typec_port(dev);
> +
> +	ida_simple_remove(&typec_index_ida, port->id);
> +	kfree(port);
> +}
> +
> +static struct device_type typec_port_dev_type = {
> +	.name = "typec_port",
> +	.groups = typec_groups,
> +	.uevent = typec_uevent,
> +	.release = typec_release,
> +};
> +
> +struct typec_altmode
> +*typec_port_register_altmode(struct typec_port *port,
> +			     struct typec_altmode_desc *desc)
> +{
> +	return typec_register_altmode(&port->dev, desc);
> +}
> +EXPORT_SYMBOL_GPL(typec_port_register_altmode);
> +
> +struct typec_port *typec_register_port(struct device *parent,
> +				       const struct typec_capability *cap)
> +{
> +	struct typec_port *port;
> +	enum typec_role role;
> +	int ret;
> +	int id;
> +
> +	port = kzalloc(sizeof(*port), GFP_KERNEL);
> +	if (!port)
> +		return NULL;
> +
> +	id = ida_simple_get(&typec_index_ida, 0, 0, GFP_KERNEL);
> +	if (id < 0) {
> +		kfree(port);
> +		return NULL;
> +	}
> +
> +	if (cap->type == TYPEC_PORT_DFP)
> +		role = TYPEC_SOURCE;
> +	else if (cap->type == TYPEC_PORT_UFP)
> +		role = TYPEC_SINK;
> +	else
> +		role = cap->prefer_role;
> +
> +	if (role == TYPEC_SOURCE) {
> +		port->data_role = TYPEC_HOST;
> +		port->pwr_role = TYPEC_SOURCE;
> +		port->vconn_role = TYPEC_SOURCE;
> +	} else {
> +		port->data_role = TYPEC_DEVICE;
> +		port->pwr_role = TYPEC_SINK;
> +		port->vconn_role = TYPEC_SINK;
> +	}
> +
> +	port->id = id;
> +	port->cap = cap;
> +	port->prefer_role = cap->prefer_role;
> +
> +	port->dev.type = &typec_port_dev_type;
> +	port->dev.class = typec_class;
> +	port->dev.parent = parent;
> +	dev_set_name(&port->dev, "port%d", id);
> +
> +	ret = device_register(&port->dev);
> +	if (ret) {
> +		dev_err(parent, "failed to register port (%d)\n", ret);
> +		ida_simple_remove(&typec_index_ida, id);
> +		put_device(&port->dev);
> +		kfree(port);
> +		return NULL;
> +	}
> +
> +	return port;
> +}
> +EXPORT_SYMBOL_GPL(typec_register_port);
> +
> +void typec_unregister_port(struct typec_port *port)
> +{
> +	if (port)
> +		device_unregister(&port->dev);
> +}
> +EXPORT_SYMBOL_GPL(typec_unregister_port);
> +
> +static int __init typec_init(void)
> +{
> +	typec_class = class_create(THIS_MODULE, "typec");
> +	if (IS_ERR(typec_class))
> +		return PTR_ERR(typec_class);
> +	return 0;
> +}
> +subsys_initcall(typec_init);
> +
> +static void __exit typec_exit(void)
> +{
> +	class_destroy(typec_class);
> +	ida_destroy(&typec_index_ida);
> +}
> +module_exit(typec_exit);
> +
> +MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>");
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("USB Type-C Connector Class");
> diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
> new file mode 100644
> index 0000000..37b996c
> --- /dev/null
> +++ b/include/linux/usb/typec.h
> @@ -0,0 +1,212 @@
> +
> +#ifndef __LINUX_USB_TYPEC_H
> +#define __LINUX_USB_TYPEC_H
> +
> +#include <linux/types.h>
> +
> +/* USB Type-C Specification releases */
> +#define USB_TYPEC_REV_1_0	0x100 /* 1.0 */
> +#define USB_TYPEC_REV_1_1	0x110 /* 1.1 */
> +#define USB_TYPEC_REV_1_2	0x120 /* 1.2 */
> +
> +struct typec_altmode;
> +struct typec_partner;
> +struct typec_cable;
> +struct typec_plug;
> +struct typec_port;
> +
> +enum typec_port_type {
> +	TYPEC_PORT_DFP,
> +	TYPEC_PORT_UFP,
> +	TYPEC_PORT_DRP,
> +};
> +
> +enum typec_plug_type {
> +	USB_PLUG_NONE,
> +	USB_PLUG_TYPE_A,
> +	USB_PLUG_TYPE_B,
> +	USB_PLUG_TYPE_C,
> +	USB_PLUG_CAPTIVE,
> +};
> +
> +enum typec_data_role {
> +	TYPEC_DEVICE,
> +	TYPEC_HOST,
> +};
> +
> +enum typec_role {
> +	TYPEC_SINK,
> +	TYPEC_SOURCE,
> +};
> +
> +enum typec_pwr_opmode {
> +	TYPEC_PWR_MODE_USB,
> +	TYPEC_PWR_MODE_1_5A,
> +	TYPEC_PWR_MODE_3_0A,
> +	TYPEC_PWR_MODE_PD,
> +};
> +
> +enum typec_accessory {
> +	TYPEC_ACCESSORY_NONE,
> +	TYPEC_ACCESSORY_AUDIO,
> +	TYPEC_ACCESSORY_DEBUG,
> +};
> +
> +/*
> + * struct typec_mode_desc - Individual Mode of an Alternate Mode
> + * @index: Index of the Mode within the SVID
> + * @vdo: VDO returned by Discover Modes USB PD command
> + * @desc: Optional human readable description of the mode
> + * @roles: Only for ports. DRP if the mode is available in both roles
> + *
> + * Description of a mode of an Alternate Mode which a connector, cable plug or
> + * partner supports. Every mode will have it's own sysfs group. The details are
> + * the VDO returned by discover modes command, description for the mode and
> + * active flag telling has the mode being entered or not.
> + */
> +struct typec_mode_desc {
> +	int			index;
> +	u32			vdo;
> +	char			*desc;
> +	/* Only used with ports */
> +	enum typec_port_type	roles;
> +};
> +
> +/*
> + * struct typec_altmode_desc - USB Type-C Alternate Mode Descriptor
> + * @svid: Standard or Vendor ID
> + * @n_modes: Number of modes
> + * @modes: Array of modes supported by the Alternate Mode
> + *
> + * Representation of an Alternate Mode that has SVID assigned by USB-IF. The
> + * array of modes will list the modes of a particular SVID that are supported by
> + * a connector, partner of a cable plug.
> + */
> +struct typec_altmode_desc {
> +	u16			svid;
> +	int			n_modes;
> +	struct typec_mode_desc	*modes;
> +};
> +
> +struct typec_altmode
> +*typec_partner_register_altmode(struct typec_partner *partner,
> +				struct typec_altmode_desc *desc);
> +struct typec_altmode
> +*typec_plug_register_altmode(struct typec_plug *plug,
> +			     struct typec_altmode_desc *desc);
> +struct typec_altmode
> +*typec_port_register_altmode(struct typec_port *port,
> +			     struct typec_altmode_desc *desc);
> +void typec_unregister_altmode(struct typec_altmode *altmode);
> +
> +struct typec_port *typec_altmode2port(struct typec_altmode *alt);
> +
> +void typec_altmode_update_active(struct typec_altmode *alt, int mode,
> +				 bool active);
> +
> +enum typec_plug_index {
> +	TYPEC_PLUG_SOP_P,
> +	TYPEC_PLUG_SOP_PP,
> +};
> +
> +/*
> + * struct typec_plug_desc - USB Type-C Cable Plug Descriptor
> + * @index: SOP Prime for the plug connected to DFP and SOP Double Prime for the
> + *         plug connected to UFP
> + *
> + * Represents USB Type-C Cable Plug.
> + */
> +struct typec_plug_desc {
> +	enum typec_plug_index	index;
> +};
> +
> +/*
> + * struct typec_cable_desc - USB Type-C Cable Descriptor
> + * @pd_revision: USB Power Delivery Specification revision
> + * @type: The plug type from USB PD Cable VDO
> + * @active: Is the cable active or passive
> + *
> + * Represents USB Type-C Cable attached to USB Type-C port.
> + */
> +struct typec_cable_desc {
> +	u16			pd_revision; /* 0300H = "3.0" */
> +	enum typec_plug_type	type;
> +	u32			vdo;
> +	unsigned int		active:1;
> +};
> +
> +/*
> + * struct typec_partner_desc - USB Type-C Partner Descriptor
> + * @pd_revision: USB Power Delivery Specification revision (0 = no USB PD)
> + * @vdo: VDO returned by Discover Identity USB PD command
> + * @accessory: Audio, Debug or none.
> + *
> + * Details about a partner that is attached to USB Type-C port.
> + */
> +struct typec_partner_desc {
> +	u16			pd_revision; /* 0300H = "3.0" */
> +	u32			vdo;
> +	enum typec_accessory	accessory;
> +};
> +
> +/*
> + * struct typec_capability - USB Type-C Port Capabilities
> + * @role: DFP (Host-only), UFP (Device-only) or DRP (Dual Role)
> + * @revision: USB Type-C Specification release. Binary coded decimal
> + * @pd_revision: USB Power Delivery Specification revision if supported
> + * @prefer_role: Initial role preference
> + * @accessory: Supported Accessory Modes (NULL terminated array)
> + * @try_role: Set data role preference for DRP port
> + * @dr_set: Set Data Role
> + * @pr_set: Set Power Role
> + * @vconn_set: Set VCONN Role
> + * @activate_mode: Enter/exit given Alternate Mode
> + *
> + * Static capabilities of a single USB Type-C port.
> + */
> +struct typec_capability {
> +	enum typec_port_type	type;
> +	u16			revision; /* 0120H = "1.2" */
> +	u16			pd_revision; /* 0300H = "3.0" */
> +	int			prefer_role;
> +	enum typec_accessory	*accessory;
> +
> +	int		(*try_role)(const struct typec_capability *,
> +				    int role);
> +
> +	int		(*dr_set)(const struct typec_capability *,
> +				  enum typec_data_role);
> +	int		(*pr_set)(const struct typec_capability *,
> +				  enum typec_role);
> +	int		(*vconn_set)(const struct typec_capability *,
> +				     enum typec_role);
> +
> +	int		(*activate_mode)(const struct typec_capability *,
> +					 int mode, int activate);
> +};
> +
> +/* Specific to try_role(). Indicates the user want's to clear the preference. */
> +#define TYPEC_NO_PREFERRED_ROLE	(-1)
> +
> +struct typec_port *typec_register_port(struct device *parent,
> +				       const struct typec_capability *cap);
> +void typec_unregister_port(struct typec_port *port);
> +
> +struct typec_partner *typec_register_partner(struct typec_port *port,
> +					     struct typec_partner_desc *desc);
> +void typec_unregister_partner(struct typec_partner *partner);
> +
> +struct typec_cable *typec_register_cable(struct typec_port *port,
> +					 struct typec_cable_desc *desc);
> +void typec_unregister_cable(struct typec_cable *cable);
> +
> +struct typec_plug *typec_register_plug(struct typec_cable *cable,
> +				       struct typec_plug_desc *desc);
> +void typec_unregister_plug(struct typec_plug *plug);
> +
> +void typec_set_data_role(struct typec_port *port, enum typec_data_role role);
> +void typec_set_pwr_role(struct typec_port *port, enum typec_role role);
> +void typec_set_vconn_role(struct typec_port *port, enum typec_role role);
> +void typec_set_pwr_opmode(struct typec_port *port, enum typec_pwr_opmode mode);
> +
> +#endif /* __LINUX_USB_TYPEC_H */
>

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [RFC PATCH] usb: USB Type-C connector class
  2016-12-19 17:22                   ` Guenter Roeck
@ 2016-12-20 14:23                     ` Heikki Krogerus
  0 siblings, 0 replies; 19+ messages in thread
From: Heikki Krogerus @ 2016-12-20 14:23 UTC (permalink / raw)
  To: Guenter Roeck; +Cc: Oliver Neukum, Greg KH, linux-kernel, linux-usb

[-- Attachment #1: Type: text/plain, Size: 2646 bytes --]

On Mon, Dec 19, 2016 at 09:22:33AM -0800, Guenter Roeck wrote:
> On 12/19/2016 06:45 AM, Heikki Krogerus wrote:
> > The purpose of USB Type-C connector class is to provide
> > unified interface for the user space to get the status and
> > basic information about USB Type-C connectors on a system,
> > control over data role swapping, and when the port supports
> > USB Power Delivery, also control over power role swapping
> > and Alternate Modes.
> > 
> > Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> > ---
> >  Documentation/ABI/testing/sysfs-class-typec |  220 ++++++
> >  Documentation/usb/typec.txt                 |  174 +++++
> >  MAINTAINERS                                 |    9 +
> >  drivers/usb/Kconfig                         |    2 +
> >  drivers/usb/Makefile                        |    2 +
> >  drivers/usb/typec/Kconfig                   |    7 +
> >  drivers/usb/typec/Makefile                  |    1 +
> >  drivers/usb/typec/typec.c                   | 1047 +++++++++++++++++++++++++++
> >  include/linux/usb/typec.h                   |  212 ++++++
> >  9 files changed, 1674 insertions(+)
> >  create mode 100644 Documentation/ABI/testing/sysfs-class-typec
> >  create mode 100644 Documentation/usb/typec.txt
> >  create mode 100644 drivers/usb/typec/Kconfig
> >  create mode 100644 drivers/usb/typec/Makefile
> >  create mode 100644 drivers/usb/typec/typec.c
> >  create mode 100644 include/linux/usb/typec.h
> > 
> > 
> > Hi,
> > 
> > There was one more change to the ABI: the ports are now named
> > "port<id>" instead of "usbc<id>". Let me know if that is OK!
> > 
> > The series is still being reviewed internally, but I'm sending this
> > already here so you guys can take a look.
> > 
> > The API now expects the drivers to register every part separately,
> > including the alternate modes for the port itself. Structures for the
> > partner/cable/plug/altmode are now protected, and the drivers need to
> > register those with separate descriptor structures and get a handle to
> > struct typec_partner/cable/plug/altmode if the registration is
> > successful, or just NULL in case of failure as Greg proposed.
> > 
> > As the drivers need to register the partners and the cables separately
> > in any case, the drivers do not declare connections with the class
> > anymore. If the default role changes during initial negotiation with a
> > partner, the drivers need to notify the class before registering the
> > partner or cable.
> > 
> 
> Can you also publish the Whiskey Cove driver ? That might help figuring out
> the necessary driver changes.

Please find it attached.

Br,

-- 
heikki

[-- Attachment #2: 0001-usb-typec-add-driver-for-Intel-Whiskey-Cove-PMIC-USB.patch --]
[-- Type: text/plain, Size: 12278 bytes --]

>From f9f906586f2ba78843683ec4c724153ab93d9ece Mon Sep 17 00:00:00 2001
From: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Date: Fri, 27 May 2016 15:23:03 +0300
Subject: [PATCH] usb: typec: add driver for Intel Whiskey Cove PMIC USB Type-C
 PHY

This adds driver for the USB Type-C PHY on Intel WhiskeyCove
PMIC which is available on some of the Intel Broxton SoC
based platforms.

Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
---
 drivers/usb/typec/Kconfig       |  14 ++
 drivers/usb/typec/Makefile      |   1 +
 drivers/usb/typec/typec_wcove.c | 377 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 392 insertions(+)
 create mode 100644 drivers/usb/typec/typec_wcove.c

diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
index 17792f9..2abbcb0 100644
--- a/drivers/usb/typec/Kconfig
+++ b/drivers/usb/typec/Kconfig
@@ -4,4 +4,18 @@ menu "USB Power Delivery and Type-C drivers"
 config TYPEC
 	tristate
 
+config TYPEC_WCOVE
+	tristate "Intel WhiskeyCove PMIC USB Type-C PHY driver"
+	depends on ACPI
+	depends on INTEL_SOC_PMIC
+	depends on INTEL_PMC_IPC
+	select TYPEC
+	help
+	  This driver adds support for USB Type-C detection on Intel Broxton
+	  platforms that have Intel Whiskey Cove PMIC. The driver can detect the
+	  role and cable orientation.
+
+	  To compile this driver as module, choose M here: the module will be
+	  called typec_wcove
+
 endmenu
diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
index 1012a8b..b9cb862 100644
--- a/drivers/usb/typec/Makefile
+++ b/drivers/usb/typec/Makefile
@@ -1 +1,2 @@
 obj-$(CONFIG_TYPEC)		+= typec.o
+obj-$(CONFIG_TYPEC_WCOVE)	+= typec_wcove.o
diff --git a/drivers/usb/typec/typec_wcove.c b/drivers/usb/typec/typec_wcove.c
new file mode 100644
index 0000000..d5a7b21
--- /dev/null
+++ b/drivers/usb/typec/typec_wcove.c
@@ -0,0 +1,377 @@
+/**
+ * typec_wcove.c - WhiskeyCove PMIC USB Type-C PHY driver
+ *
+ * Copyright (C) 2017 Intel Corporation
+ * Author: Heikki Krogerus <heikki.krogerus@linux.intel.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 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/acpi.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/usb/typec.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/intel_soc_pmic.h>
+
+/* Register offsets */
+#define WCOVE_CHGRIRQ0		0x4e09
+#define WCOVE_PHYCTRL		0x5e07
+
+#define USBC_CONTROL1		0x7001
+#define USBC_CONTROL2		0x7002
+#define USBC_CONTROL3		0x7003
+#define USBC_CC1_CTRL		0x7004
+#define USBC_CC2_CTRL		0x7005
+#define USBC_STATUS1		0x7007
+#define USBC_STATUS2		0x7008
+#define USBC_STATUS3		0x7009
+#define USBC_IRQ1		0x7015
+#define USBC_IRQ2		0x7016
+#define USBC_IRQMASK1		0x7017
+#define USBC_IRQMASK2		0x7018
+
+/* Register bits */
+
+#define USBC_CONTROL1_MODE_DRP(r)	(((r) & ~0x7) | 4)
+
+#define USBC_CONTROL2_UNATT_SNK		BIT(0)
+#define USBC_CONTROL2_UNATT_SRC		BIT(1)
+#define USBC_CONTROL2_DIS_ST		BIT(2)
+
+#define USBC_CONTROL3_PD_DIS		BIT(1)
+
+#define USBC_CC_CTRL_VCONN_EN		BIT(1)
+
+#define USBC_STATUS1_DET_ONGOING	BIT(6)
+#define USBC_STATUS1_RSLT(r)		((r) & 0xf)
+#define USBC_RSLT_NOTHING		0
+#define USBC_RSLT_SRC_DEFAULT		1
+#define USBC_RSLT_SRC_1_5A		2
+#define USBC_RSLT_SRC_3_0A		3
+#define USBC_RSLT_SNK			4
+#define USBC_RSLT_DEBUG_ACC		5
+#define USBC_RSLT_AUDIO_ACC		6
+#define USBC_RSLT_UNDEF			15
+#define USBC_STATUS1_ORIENT(r)		(((r) >> 4) & 0x3)
+#define USBC_ORIENT_NORMAL		1
+#define USBC_ORIENT_REVERSE		2
+
+#define USBC_STATUS2_VBUS_REQ		BIT(5)
+
+#define USBC_IRQ1_ADCDONE1		BIT(2)
+#define USBC_IRQ1_OVERTEMP		BIT(1)
+#define USBC_IRQ1_SHORT			BIT(0)
+
+#define USBC_IRQ2_CC_CHANGE		BIT(7)
+#define USBC_IRQ2_RX_PD			BIT(6)
+#define USBC_IRQ2_RX_HR			BIT(5)
+#define USBC_IRQ2_RX_CR			BIT(4)
+#define USBC_IRQ2_TX_SUCCESS		BIT(3)
+#define USBC_IRQ2_TX_FAIL		BIT(2)
+
+#define USBC_IRQMASK1_ALL	(USBC_IRQ1_ADCDONE1 | USBC_IRQ1_OVERTEMP | \
+				 USBC_IRQ1_SHORT)
+
+#define USBC_IRQMASK2_ALL	(USBC_IRQ2_CC_CHANGE | USBC_IRQ2_RX_PD | \
+				 USBC_IRQ2_RX_HR | USBC_IRQ2_RX_CR | \
+				 USBC_IRQ2_TX_SUCCESS | USBC_IRQ2_TX_FAIL)
+
+struct wcove_typec {
+	struct mutex lock; /* device lock */
+	struct device *dev;
+	struct regmap *regmap;
+	struct typec_port *port;
+	struct typec_capability cap;
+	struct typec_partner *partner;
+};
+
+enum wcove_typec_func {
+	WCOVE_FUNC_DRIVE_VBUS = 1,
+	WCOVE_FUNC_ORIENTATION,
+	WCOVE_FUNC_ROLE,
+	WCOVE_FUNC_DRIVE_VCONN,
+};
+
+enum wcove_typec_orientation {
+	WCOVE_ORIENTATION_NORMAL,
+	WCOVE_ORIENTATION_REVERSE,
+};
+
+enum wcove_typec_role {
+	WCOVE_ROLE_HOST,
+	WCOVE_ROLE_DEVICE,
+};
+
+static uuid_le uuid = UUID_LE(0x482383f0, 0x2876, 0x4e49,
+			      0x86, 0x85, 0xdb, 0x66, 0x21, 0x1a, 0xf0, 0x37);
+
+static int wcove_typec_func(struct wcove_typec *wcove,
+			    enum wcove_typec_func func, int param)
+{
+	union acpi_object *obj;
+	union acpi_object tmp;
+	union acpi_object argv4 = ACPI_INIT_DSM_ARGV4(1, &tmp);
+
+	tmp.type = ACPI_TYPE_INTEGER;
+	tmp.integer.value = param;
+
+	obj = acpi_evaluate_dsm(ACPI_HANDLE(wcove->dev), uuid.b, 1, func,
+				&argv4);
+	if (!obj) {
+		dev_err(wcove->dev, "%s: failed to evaluate _DSM\n", __func__);
+		return -EIO;
+	}
+
+	ACPI_FREE(obj);
+	return 0;
+}
+
+static irqreturn_t wcove_typec_irq(int irq, void *data)
+{
+	enum typec_role role = TYPEC_SINK;
+	struct typec_partner_desc partner;
+	struct wcove_typec *wcove = data;
+	unsigned int cc1_ctrl;
+	unsigned int cc2_ctrl;
+	unsigned int cc_irq1;
+	unsigned int cc_irq2;
+	unsigned int status1;
+	unsigned int status2;
+	int ret;
+
+	mutex_lock(&wcove->lock);
+
+	ret = regmap_read(wcove->regmap, USBC_IRQ1, &cc_irq1);
+	if (ret)
+		goto err;
+
+	ret = regmap_read(wcove->regmap, USBC_IRQ2, &cc_irq2);
+	if (ret)
+		goto err;
+
+	ret = regmap_read(wcove->regmap, USBC_STATUS1, &status1);
+	if (ret)
+		goto err;
+
+	ret = regmap_read(wcove->regmap, USBC_STATUS2, &status2);
+	if (ret)
+		goto err;
+
+	ret = regmap_read(wcove->regmap, USBC_CC1_CTRL, &cc1_ctrl);
+	if (ret)
+		goto err;
+
+	ret = regmap_read(wcove->regmap, USBC_CC2_CTRL, &cc2_ctrl);
+	if (ret)
+		goto err;
+
+	if (cc_irq1) {
+		if (cc_irq1 & USBC_IRQ1_OVERTEMP)
+			dev_err(wcove->dev, "VCONN Switch Over Temperature!\n");
+		if (cc_irq1 & USBC_IRQ1_SHORT)
+			dev_err(wcove->dev, "VCONN Switch Short Circuit!\n");
+		ret = regmap_write(wcove->regmap, USBC_IRQ1, cc_irq1);
+		if (ret)
+			goto err;
+	}
+
+	if (cc_irq2) {
+		ret = regmap_write(wcove->regmap, USBC_IRQ2, cc_irq2);
+		if (ret)
+			goto err;
+		/*
+		 * Ignoring any PD communication interrupts until the PD support
+		 * is available
+		 */
+		if (cc_irq2 & ~USBC_IRQ2_CC_CHANGE) {
+			dev_WARN(wcove->dev, "USB PD handling missing\n");
+			goto err;
+		}
+	}
+
+	if (status1 & USBC_STATUS1_DET_ONGOING)
+		goto out;
+
+	if (USBC_STATUS1_RSLT(status1) == USBC_RSLT_NOTHING) {
+		if (wcove->partner) {
+			typec_unregister_partner(wcove->partner);
+			wcove->partner = NULL;
+		}
+
+		wcove_typec_func(wcove, WCOVE_FUNC_ORIENTATION,
+				 WCOVE_ORIENTATION_NORMAL);
+
+		/* This makes sure the device controller is disconnected */
+		wcove_typec_func(wcove, WCOVE_FUNC_ROLE, WCOVE_ROLE_HOST);
+
+		/* Port to default role */
+		typec_set_data_role(wcove->port, TYPEC_DEVICE);
+		typec_set_pwr_role(wcove->port, TYPEC_SINK);
+		typec_set_pwr_opmode(wcove->port, TYPEC_PWR_MODE_USB);
+
+		goto out;
+	}
+
+	if (wcove->partner)
+		goto out;
+
+	switch (USBC_STATUS1_ORIENT(status1)) {
+	case USBC_ORIENT_NORMAL:
+		wcove_typec_func(wcove, WCOVE_FUNC_ORIENTATION,
+				 WCOVE_ORIENTATION_NORMAL);
+		break;
+	case USBC_ORIENT_REVERSE:
+		wcove_typec_func(wcove, WCOVE_FUNC_ORIENTATION,
+				 WCOVE_ORIENTATION_REVERSE);
+	default:
+		break;
+	}
+
+	memset(&partner, 0, sizeof(partner));
+
+	switch (USBC_STATUS1_RSLT(status1)) {
+	case USBC_RSLT_SRC_DEFAULT:
+		typec_set_pwr_opmode(wcove->port, TYPEC_PWR_MODE_USB);
+		break;
+	case USBC_RSLT_SRC_1_5A:
+		typec_set_pwr_opmode(wcove->port, TYPEC_PWR_MODE_1_5A);
+		break;
+	case USBC_RSLT_SRC_3_0A:
+		typec_set_pwr_opmode(wcove->port, TYPEC_PWR_MODE_3_0A);
+		break;
+	case USBC_RSLT_SNK:
+		role = TYPEC_SOURCE;
+		break;
+	case USBC_RSLT_DEBUG_ACC:
+		partner.accessory = TYPEC_ACCESSORY_DEBUG;
+		break;
+	case USBC_RSLT_AUDIO_ACC:
+		partner.accessory = TYPEC_ACCESSORY_AUDIO;
+		break;
+	default:
+		dev_WARN(wcove->dev, "%s Undefined result\n", __func__);
+		goto err;
+	}
+
+	if (role == TYPEC_SINK) {
+		wcove_typec_func(wcove, WCOVE_FUNC_ROLE, WCOVE_ROLE_DEVICE);
+		typec_set_data_role(wcove->port, TYPEC_DEVICE);
+		typec_set_pwr_role(wcove->port, TYPEC_SINK);
+	} else {
+		wcove_typec_func(wcove, WCOVE_FUNC_ROLE, WCOVE_ROLE_HOST);
+		typec_set_pwr_role(wcove->port, TYPEC_SOURCE);
+		typec_set_data_role(wcove->port, TYPEC_HOST);
+	}
+
+	wcove->partner = typec_register_partner(wcove->port, &partner);
+	if (!wcove->partner)
+		dev_err(wcove->dev, "failed register partner\n");
+out:
+	/* If either CC pins is requesting VCONN, we turn it on */
+	if ((cc1_ctrl & USBC_CC_CTRL_VCONN_EN) ||
+	    (cc2_ctrl &	USBC_CC_CTRL_VCONN_EN))
+		wcove_typec_func(wcove, WCOVE_FUNC_DRIVE_VCONN, true);
+	else
+		wcove_typec_func(wcove, WCOVE_FUNC_DRIVE_VCONN, false);
+
+	/* Relying on the FSM to know when we need to drive VBUS. */
+	wcove_typec_func(wcove, WCOVE_FUNC_DRIVE_VBUS,
+			 !!(status2 & USBC_STATUS2_VBUS_REQ));
+err:
+	/* REVISIT: Clear WhiskeyCove CHGR Type-C interrupt */
+	regmap_write(wcove->regmap, WCOVE_CHGRIRQ0, BIT(5));
+
+	mutex_unlock(&wcove->lock);
+	return IRQ_HANDLED;
+}
+
+static int wcove_typec_probe(struct platform_device *pdev)
+{
+	struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent);
+	struct wcove_typec *wcove;
+	unsigned int val;
+	int ret;
+
+	wcove = devm_kzalloc(&pdev->dev, sizeof(*wcove), GFP_KERNEL);
+	if (!wcove)
+		return -ENOMEM;
+
+	mutex_init(&wcove->lock);
+	wcove->dev = &pdev->dev;
+	wcove->regmap = pmic->regmap;
+
+	ret = regmap_irq_get_virq(pmic->irq_chip_data_level2,
+				  platform_get_irq(pdev, 0));
+	if (ret < 0)
+		return ret;
+
+	ret = devm_request_threaded_irq(&pdev->dev, ret, NULL,
+					wcove_typec_irq, IRQF_ONESHOT,
+					"wcove_typec", wcove);
+	if (ret)
+		return ret;
+
+	if (!acpi_check_dsm(ACPI_HANDLE(&pdev->dev), uuid.b, 0, 0x1f)) {
+		dev_err(&pdev->dev, "Missing _DSM functions\n");
+		return -ENODEV;
+	}
+
+	wcove->cap.type = TYPEC_PORT_DRP;
+	wcove->cap.revision = USB_TYPEC_REV_1_1;
+	wcove->cap.prefer_role = TYPEC_NO_PREFERRED_ROLE;
+
+	/* Make sure the PD PHY is disabled until USB PD is available */
+	regmap_read(wcove->regmap, USBC_CONTROL3, &val);
+	regmap_write(wcove->regmap, USBC_CONTROL3, val | USBC_CONTROL3_PD_DIS);
+
+	/* DRP mode without accessory support */
+	regmap_read(wcove->regmap, USBC_CONTROL1, &val);
+	regmap_write(wcove->regmap, USBC_CONTROL1, USBC_CONTROL1_MODE_DRP(val));
+
+	wcove->port = typec_register_port(&pdev->dev, &wcove->cap);
+	if (!wcove->port)
+		return -ENODEV;
+
+	/* Unmask everything */
+	regmap_read(wcove->regmap, USBC_IRQMASK1, &val);
+	regmap_write(wcove->regmap, USBC_IRQMASK1, val & ~USBC_IRQMASK1_ALL);
+	regmap_read(wcove->regmap, USBC_IRQMASK2, &val);
+	regmap_write(wcove->regmap, USBC_IRQMASK2, val & ~USBC_IRQMASK2_ALL);
+
+	platform_set_drvdata(pdev, wcove);
+	return 0;
+}
+
+static int wcove_typec_remove(struct platform_device *pdev)
+{
+	struct wcove_typec *wcove = platform_get_drvdata(pdev);
+	unsigned int val;
+
+	/* Mask everything */
+	regmap_read(wcove->regmap, USBC_IRQMASK1, &val);
+	regmap_write(wcove->regmap, USBC_IRQMASK1, val | USBC_IRQMASK1_ALL);
+	regmap_read(wcove->regmap, USBC_IRQMASK2, &val);
+	regmap_write(wcove->regmap, USBC_IRQMASK2, val | USBC_IRQMASK2_ALL);
+
+	typec_unregister_partner(wcove->partner);
+	typec_unregister_port(wcove->port);
+	return 0;
+}
+
+static struct platform_driver wcove_typec_driver = {
+	.driver = {
+		.name		= "bxt_wcove_usbc",
+	},
+	.probe			= wcove_typec_probe,
+	.remove			= wcove_typec_remove,
+};
+
+module_platform_driver(wcove_typec_driver);
+
+MODULE_AUTHOR("Intel Corporation");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("WhiskeyCove PMIC USB Type-C PHY driver");
+MODULE_ALIAS("platform:bxt_wcove_usbc");
-- 
2.10.2


^ permalink raw reply related	[flat|nested] 19+ messages in thread

end of thread, other threads:[~2016-12-20 14:23 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-11-24 12:21 [PATCHv13 0/3] USB Type-C Connector class Heikki Krogerus
2016-11-24 12:21 ` [PATCHv13 1/3] lib/string: add sysfs_match_string helper Heikki Krogerus
2016-12-07 23:54   ` [PATCHv13,1/3] " Guenter Roeck
2016-12-08  7:29     ` Heikki Krogerus
2016-11-24 12:21 ` [PATCHv13 2/3] usb: USB Type-C connector class Heikki Krogerus
2016-11-29 16:27   ` Greg KH
2016-11-30  9:19     ` Heikki Krogerus
2016-12-02 18:04       ` Guenter Roeck
2016-12-02 18:22         ` Greg KH
2016-12-07  9:11         ` Heikki Krogerus
2016-12-07  9:46         ` Oliver Neukum
2016-12-07 12:52           ` Heikki Krogerus
2016-12-15 11:50             ` Heikki Krogerus
2016-12-15 12:06               ` Oliver Neukum
2016-12-15 15:01               ` Guenter Roeck
2016-12-19 14:45                 ` [RFC PATCH] " Heikki Krogerus
2016-12-19 17:22                   ` Guenter Roeck
2016-12-20 14:23                     ` Heikki Krogerus
2016-11-24 12:21 ` [PATCHv13 3/3] usb: typec: add driver for Intel Whiskey Cove PMIC USB Type-C PHY Heikki Krogerus

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).