linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCHv6 0/3] USB Type-C Connector class
@ 2016-08-22 12:05 Heikki Krogerus
  2016-08-22 12:05 ` [PATCHv6 1/3] usb: USB Type-C connector class Heikki Krogerus
                   ` (2 more replies)
  0 siblings, 3 replies; 28+ messages in thread
From: Heikki Krogerus @ 2016-08-22 12:05 UTC (permalink / raw)
  To: Greg KH, Guenter Roeck
  Cc: Oliver Neukum, Felipe Balbi, Bin Gao, 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 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):
  usb: USB Type-C connector class
  usb: typec: add driver for Intel Whiskey Cove PMIC USB Type-C PHY
  mfd: intel_soc_pmic_bxtwc: add support for USB Type-C PHY on
    WhiskeyCove

 Documentation/ABI/testing/sysfs-class-typec |  199 +++++
 Documentation/usb/typec.txt                 |  103 +++
 MAINTAINERS                                 |    9 +
 drivers/mfd/intel_soc_pmic_bxtwc.c          |   11 +
 drivers/usb/Kconfig                         |    2 +
 drivers/usb/Makefile                        |    2 +
 drivers/usb/typec/Kconfig                   |   21 +
 drivers/usb/typec/Makefile                  |    2 +
 drivers/usb/typec/typec.c                   | 1090 +++++++++++++++++++++++++++
 drivers/usb/typec/typec_wcove.c             |  372 +++++++++
 include/linux/usb/typec.h                   |  260 +++++++
 11 files changed, 2071 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.8.1

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

* [PATCHv6 1/3] usb: USB Type-C connector class
  2016-08-22 12:05 [PATCHv6 0/3] USB Type-C Connector class Heikki Krogerus
@ 2016-08-22 12:05 ` Heikki Krogerus
  2016-08-24 14:08   ` Vincent Palatin
  2016-08-22 12:05 ` [PATCHv6 2/3] usb: typec: add driver for Intel Whiskey Cove PMIC USB Type-C PHY Heikki Krogerus
  2016-08-22 12:06 ` [PATCHv6 3/3] mfd: intel_soc_pmic_bxtwc: add support for USB Type-C PHY on WhiskeyCove Heikki Krogerus
  2 siblings, 1 reply; 28+ messages in thread
From: Heikki Krogerus @ 2016-08-22 12:05 UTC (permalink / raw)
  To: Greg KH, Guenter Roeck
  Cc: Oliver Neukum, Felipe Balbi, Bin Gao, 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 |  199 +++++
 Documentation/usb/typec.txt                 |  103 +++
 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                   | 1090 +++++++++++++++++++++++++++
 include/linux/usb/typec.h                   |  260 +++++++
 9 files changed, 1673 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..e6179d3
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-typec
@@ -0,0 +1,199 @@
+USB Type-C port devices (eg. /sys/class/typec/usbc0/)
+
+What:		/sys/class/typec/<port>/current_data_role
+Date:		June 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.
+
+		Valid values:
+		- host
+		- device
+
+What:		/sys/class/typec/<port>/current_power_role
+Date:		June 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.
+
+		Valid values:
+		- source
+		- sink
+
+What:		/sys/class/typec/<port>/current_vconn_role
+Date:		June 2016
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows the current VCONN role of the port. This attribute can be
+		used to request VCONN role swap on the port when the port
+		supports USB Power Delivery.
+
+		Valid values are:
+		- source
+		- sink
+
+What:		/sys/class/typec/<port>/power_operation_mode
+Date:		June 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
+		- BC1.2 - Power levels defined in Battery Charging Specification
+			  v1.2
+		- 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:		June 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:
+		- host
+		- device
+		- For example "none" to remove preference (anything else except
+		  "host" or "device")
+
+What:		/sys/class/typec/<port>/supported_accessory_modes
+Date:		June 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:		June 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:		June 2016
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Lists the power roles the port is capable of supporting.
+
+		Valid values:
+		- source
+		- sink
+
+What:		/sys/class/typec/<port>/supports_usb_power_delivery
+Date:		June 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
+Date:		June 2016
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		The attribute is visible only when the partner's type is
+		"Accessory". The type can be read from its own attribute.
+
+		Shows the name of the Accessory Mode. The Accessory Modes are
+		defined in USB Type-C Specification.
+
+What:		/sys/class/typec/<port>-partner/type
+Date:		June 2016
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows the type of the partner. Can be one of the following:
+		- USB - When the partner is normal USB host/peripheral.
+		- Charger - When the partner has been identified as dedicated
+			    charger.
+		- Alternate Mode - When the partner supports Alternate Modes.
+		- Accessory - When the partner is one of the accessories with
+			      specific Accessory Mode defined in USB Type-C
+			      specification.
+
+
+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:		June 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:		June 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
+
+
+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:		June 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.
+
+What:		/sys/class/typec/<dev>/<dev>.svid:<svid>/<mode>/description
+Date:		June 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:		June 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:		June 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..dce5f07
--- /dev/null
+++ b/Documentation/usb/typec.txt
@@ -0,0 +1,103 @@
+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 patch 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);
+
+
+* 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 320cce8..bc563e3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12339,6 +12339,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 8689dcb..f42a3d3 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -150,6 +150,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 && USB_COMMON && LEDS_TRIGGERS
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index dca7856..51e381e 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile
@@ -61,3 +61,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..b229fb9
--- /dev/null
+++ b/drivers/usb/typec/Kconfig
@@ -0,0 +1,7 @@
+
+menu "USB PD 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..f4c57f0
--- /dev/null
+++ b/drivers/usb/typec/typec.c
@@ -0,0 +1,1090 @@
+/*
+ * 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)
+
+static DEFINE_IDA(typec_index_ida);
+
+static struct class typec_class = {
+	.name = "typec",
+};
+
+static const char * const typec_accessory_modes[] = {
+	[TYPEC_ACCESSORY_NONE]		= "none",
+	[TYPEC_ACCESSORY_AUDIO]		= "Audio",
+	[TYPEC_ACCESSORY_DEBUG]		= "Debug",
+	[TYPEC_ACCESSORY_DAUDIO]	= "Digital Audio",
+};
+
+static int sysfs_strmatch(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;
+}
+
+/* ------------------------------------------------------------------------- */
+/* Type-C Partners */
+
+static void typec_dev_release(struct device *dev)
+{
+}
+
+static const char * const typec_partner_types[] = {
+	[TYPEC_PARTNER_USB]		= "USB",
+	[TYPEC_PARTNER_CHARGER]		= "Charger",
+	[TYPEC_PARTNER_ALTMODE]		= "Alternate Mode",
+	[TYPEC_PARTNER_ACCESSORY]	= "Accessory",
+};
+
+static ssize_t partner_type_show(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct typec_partner *partner = container_of(dev, struct typec_partner,
+						     dev);
+
+	return sprintf(buf, "%s\n", typec_partner_types[partner->type]);
+}
+
+static struct device_attribute dev_attr_partner_type = {
+	.attr = {
+		.name = "type",
+		.mode = S_IRUGO,
+	},
+	.show = partner_type_show,
+};
+
+static ssize_t
+partner_accessory_mode_show(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct typec_partner *partner = container_of(dev, struct typec_partner,
+						     dev);
+
+	return sprintf(buf, "%s\n", typec_accessory_modes[partner->accessory]);
+}
+
+static struct device_attribute dev_attr_partner_accessory = {
+	.attr = {
+		.name = "accessory",
+		.mode = S_IRUGO,
+	},
+	.show = partner_accessory_mode_show,
+};
+
+static struct attribute *typec_partner_attrs[] = {
+	&dev_attr_partner_accessory.attr,
+	&dev_attr_partner_type.attr,
+	NULL
+};
+
+static umode_t
+partner_is_visible(struct kobject *kobj, struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct typec_partner *partner = container_of(dev, struct typec_partner,
+						     dev);
+
+	if (attr == &dev_attr_partner_accessory.attr &&
+	    partner->type != TYPEC_PARTNER_ACCESSORY)
+		return 0;
+
+	return attr->mode;
+}
+
+static struct attribute_group typec_partner_group = {
+	.attrs = typec_partner_attrs,
+	.is_visible = partner_is_visible,
+};
+
+static const struct attribute_group *typec_partner_groups[] = {
+	&typec_partner_group,
+	NULL
+};
+
+static struct device_type typec_partner_dev_type = {
+	.name = "typec_partner_device",
+	.groups = typec_partner_groups,
+	.release = typec_dev_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)
+{
+	WARN_ON(port->partner->alt_modes);
+	device_unregister(&port->partner->dev);
+}
+
+/* ------------------------------------------------------------------------- */
+/* Type-C Cable Plugs */
+
+static struct device_type typec_plug_dev_type = {
+	.name = "typec_plug_device",
+	.release = typec_dev_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)
+{
+	WARN_ON(plug->alt_modes);
+	device_unregister(&plug->dev);
+}
+
+/* Type-C Cables */
+
+static ssize_t
+cable_active_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct typec_cable *cable = container_of(dev, struct typec_cable, dev);
+
+	return sprintf(buf, "%d\n", cable->active);
+}
+
+static struct device_attribute dev_attr_cable_active = {
+	.attr = {
+		.name = "active",
+		.mode = S_IRUGO,
+	},
+	.show = cable_active_show,
+};
+
+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
+cable_plug_type_show(struct device *dev, struct device_attribute *attr,
+		     char *buf)
+{
+	struct typec_cable *cable = container_of(dev, struct typec_cable, dev);
+
+	return sprintf(buf, "%s\n", typec_plug_types[cable->type]);
+}
+
+static struct device_attribute dev_attr_plug_type = {
+	.attr = {
+		.name = "plug_type",
+		.mode = S_IRUGO,
+	},
+	.show = cable_plug_type_show,
+};
+
+static struct attribute *typec_cable_attrs[] = {
+	&dev_attr_cable_active.attr,
+	&dev_attr_plug_type.attr,
+	NULL
+};
+
+static struct attribute_group typec_cable_group = {
+	.attrs = typec_cable_attrs,
+};
+
+static const struct attribute_group *typec_cable_groups[] = {
+	&typec_cable_group,
+	NULL
+};
+
+static struct device_type typec_cable_dev_type = {
+	.name = "typec_cable_device",
+	.groups = typec_cable_groups,
+	.release = typec_dev_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;
+
+	kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
+
+	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->partner = NULL;
+	port->cable = NULL;
+
+	port->pwr_opmode = TYPEC_PWR_MODE_USB;
+
+	typec_init_roles(port);
+
+	kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
+}
+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");
+}
+EXPORT_SYMBOL(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");
+}
+EXPORT_SYMBOL(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");
+}
+EXPORT_SYMBOL(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");
+}
+EXPORT_SYMBOL(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;
+	sprintf(dir, "mode%d", mode);
+	sysfs_notify(&alt->dev.kobj, dir, "active");
+}
+EXPORT_SYMBOL(typec_altmode_update_active);
+
+static struct device_type typec_port_dev_type;
+
+/*
+ * 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;
+
+	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 = S_IRUGO;
+		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 = S_IRUGO;
+		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 = S_IWUSR | S_IRUGO;
+		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 = S_IRUGO;
+			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 attibute_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 = container_of(dev,
+						       struct typec_partner,
+						       dev);
+		p->alt_modes = alt_modes;
+	} else if (dev->type == &typec_plug_dev_type) {
+		struct typec_plug *p = container_of(dev, struct 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 = container_of(dev,
+						       struct typec_partner,
+						       dev);
+		alt_modes = p->alt_modes;
+		p->alt_modes = NULL;
+	} else if (dev->type == &typec_plug_dev_type) {
+		struct typec_plug *p = container_of(dev, struct 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);
+	enum typec_role 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;
+	}
+
+	ret = sysfs_strmatch(typec_roles, ARRAY_SIZE(typec_roles), buf);
+	if (ret < 0) {
+		port->prefer_role = -1;
+		return size;
+	}
+
+	role = ret;
+
+	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);
+	enum typec_role role;
+	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_strmatch(typec_data_roles, ARRAY_SIZE(typec_data_roles),
+			     buf);
+	if (ret < 0)
+		return ret;
+
+	role = ret;
+
+	ret = port->cap->dr_set(port->cap, role);
+	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);
+	enum typec_role role;
+	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_strmatch(typec_roles, ARRAY_SIZE(typec_roles), buf);
+	if (ret < 0)
+		return ret;
+
+	role = ret;
+
+	ret = port->cap->pr_set(port->cap, role);
+	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 current_vconn_role_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;
+	}
+
+	ret = sysfs_strmatch(typec_roles, ARRAY_SIZE(typec_roles), buf);
+	if (ret < 0)
+		return ret;
+
+	role = ret;
+
+	ret = port->cap->vconn_set(port->cap, role);
+	if (ret)
+		return ret;
+
+	return size;
+}
+
+static ssize_t current_vconn_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->vconn_role]);
+}
+static DEVICE_ATTR_RW(current_vconn_role);
+
+static ssize_t supported_accessory_modes_show(struct device *dev,
+					      struct device_attribute *attr,
+					      char *buf)
+{
+	struct typec_port *port = to_typec_port(dev);
+	enum typec_accessory *accessory;
+	ssize_t ret = 0;
+	int i;
+
+	if (port->cap->accessory)
+		for (accessory = port->cap->accessory, i = 0;
+		     i < port->cap->num_accessory; accessory++, i++)
+			ret += sprintf(buf, "%s\n",
+				       typec_accessory_modes[*accessory]);
+	return ret;
+}
+static DEVICE_ATTR_RO(supported_accessory_modes);
+
+static ssize_t supports_usb_power_delivery_show(struct device *dev,
+						struct device_attribute *attr,
+						char *buf)
+{
+	struct typec_port *port = to_typec_port(dev);
+
+	return sprintf(buf, "%d\n", port->cap->usb_pd);
+}
+static DEVICE_ATTR_RO(supports_usb_power_delivery);
+
+static struct attribute *typec_attrs[] = {
+	&dev_attr_current_power_role.attr,
+	&dev_attr_current_vconn_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,
+	NULL,
+};
+
+static const struct attribute_group typec_group = {
+	.attrs = typec_attrs,
+};
+
+static const struct attribute_group *typec_groups[] = {
+	&typec_group,
+	NULL,
+};
+
+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);
+	}
+
+	/* FIXME: a better approach for this */
+	port->prefer_role = -1;
+
+	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)
+{
+	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..eda6747
--- /dev/null
+++ b/include/linux/usb/typec.h
@@ -0,0 +1,260 @@
+
+#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_partner_type {
+	TYPEC_PARTNER_USB,
+	TYPEC_PARTNER_CHARGER,
+	TYPEC_PARTNER_ALTMODE,
+	TYPEC_PARTNER_ACCESSORY,
+};
+
+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,
+	TYPEC_ACCESSORY_DAUDIO,
+};
+
+/*
+ * 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 *);
+
+void typec_altmode_update_active(struct typec_altmode *alt, int mode,
+				 bool active);
+
+int typec_register_altmodes(struct device *, struct typec_altmode *);
+void typec_unregister_altmodes(struct device *);
+
+/*
+ * 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;
+	enum typec_partner_type	type;
+	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
+ * @accessory: Supported Accessory Modes
+ * @num_accessory: Number of supported Accessory Modes
+ * @alt_modes: Alternate Modes the connector supports (null terminated)
+ * @try_role: Set a fixed data role 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;
+	enum typec_accessory	*accessory;
+	unsigned int		num_accessory;
+	struct typec_altmode	*alt_modes;
+
+	int			(*try_role)(const struct typec_capability *,
+					    enum typec_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);
+};
+
+/*
+ * 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 *, 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);
+
+#endif /* __LINUX_USB_TYPEC_H */
-- 
2.8.1

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

* [PATCHv6 2/3] usb: typec: add driver for Intel Whiskey Cove PMIC USB Type-C PHY
  2016-08-22 12:05 [PATCHv6 0/3] USB Type-C Connector class Heikki Krogerus
  2016-08-22 12:05 ` [PATCHv6 1/3] usb: USB Type-C connector class Heikki Krogerus
@ 2016-08-22 12:05 ` Heikki Krogerus
  2016-08-22 12:06 ` [PATCHv6 3/3] mfd: intel_soc_pmic_bxtwc: add support for USB Type-C PHY on WhiskeyCove Heikki Krogerus
  2 siblings, 0 replies; 28+ messages in thread
From: Heikki Krogerus @ 2016-08-22 12:05 UTC (permalink / raw)
  To: Greg KH, Guenter Roeck
  Cc: Oliver Neukum, Felipe Balbi, Bin Gao, 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>
---
 drivers/usb/typec/Kconfig       |  14 ++
 drivers/usb/typec/Makefile      |   1 +
 drivers/usb/typec/typec_wcove.c | 372 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 387 insertions(+)
 create mode 100644 drivers/usb/typec/typec_wcove.c

diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
index b229fb9..7a345a4 100644
--- a/drivers/usb/typec/Kconfig
+++ b/drivers/usb/typec/Kconfig
@@ -4,4 +4,18 @@ menu "USB PD 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..7116491
--- /dev/null
+++ b/drivers/usb/typec/typec_wcove.c
@@ -0,0 +1,372 @@
+/**
+ * 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->partner.type = TYPEC_PARTNER_USB;
+	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");
+		regmap_write(wcove->regmap, USBC_IRQ1, cc_irq1);
+	}
+
+	if (cc_irq2) {
+		regmap_write(wcove->regmap, USBC_IRQ2, cc_irq2);
+		/*
+		 * 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->partner.type = TYPEC_PARTNER_USB;
+		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->partner.type = TYPEC_PARTNER_ACCESSORY;
+		wcove->con.partner = &wcove->partner;
+		typec_connect(wcove->port, &wcove->con);
+		break;
+	case USBC_RSLT_AUDIO_ACC:
+		wcove->partner.accessory = TYPEC_ACCESSORY_AUDIO;
+		wcove->partner.type = TYPEC_PARTNER_ACCESSORY;
+		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->port = typec_register_port(&pdev->dev, &wcove->cap);
+	if (IS_ERR(wcove->port))
+		return PTR_ERR(wcove->port);
+
+	/* 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));
+
+	/* 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.8.1

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

* [PATCHv6 3/3] mfd: intel_soc_pmic_bxtwc: add support for USB Type-C PHY on WhiskeyCove
  2016-08-22 12:05 [PATCHv6 0/3] USB Type-C Connector class Heikki Krogerus
  2016-08-22 12:05 ` [PATCHv6 1/3] usb: USB Type-C connector class Heikki Krogerus
  2016-08-22 12:05 ` [PATCHv6 2/3] usb: typec: add driver for Intel Whiskey Cove PMIC USB Type-C PHY Heikki Krogerus
@ 2016-08-22 12:06 ` Heikki Krogerus
  2 siblings, 0 replies; 28+ messages in thread
From: Heikki Krogerus @ 2016-08-22 12:06 UTC (permalink / raw)
  To: Greg KH, Guenter Roeck
  Cc: Oliver Neukum, Felipe Balbi, Bin Gao, linux-kernel, linux-usb, Lee Jones

Intel WhiskeyCove PMIC has also a USB Type-C PHY, so let's
create a device for it.

Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Cc: Lee Jones <lee.jones@linaro.org>
---
 drivers/mfd/intel_soc_pmic_bxtwc.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/drivers/mfd/intel_soc_pmic_bxtwc.c b/drivers/mfd/intel_soc_pmic_bxtwc.c
index b942876..0e61dde 100644
--- a/drivers/mfd/intel_soc_pmic_bxtwc.c
+++ b/drivers/mfd/intel_soc_pmic_bxtwc.c
@@ -84,6 +84,7 @@ enum bxtwc_irqs_level2 {
 	BXTWC_THRM2_IRQ,
 	BXTWC_BCU_IRQ,
 	BXTWC_ADC_IRQ,
+	BXTWC_USBC_IRQ,
 	BXTWC_CHGR0_IRQ,
 	BXTWC_CHGR1_IRQ,
 	BXTWC_GPIO0_IRQ,
@@ -109,6 +110,7 @@ static const struct regmap_irq bxtwc_regmap_irqs_level2[] = {
 	REGMAP_IRQ_REG(BXTWC_THRM2_IRQ, 2, 0xff),
 	REGMAP_IRQ_REG(BXTWC_BCU_IRQ, 3, 0x1f),
 	REGMAP_IRQ_REG(BXTWC_ADC_IRQ, 4, 0xff),
+	REGMAP_IRQ_REG(BXTWC_USBC_IRQ, 5, BIT(5)),
 	REGMAP_IRQ_REG(BXTWC_CHGR0_IRQ, 5, 0x1f),
 	REGMAP_IRQ_REG(BXTWC_CHGR1_IRQ, 6, 0x1f),
 	REGMAP_IRQ_REG(BXTWC_GPIO0_IRQ, 7, 0xff),
@@ -143,6 +145,10 @@ static struct resource adc_resources[] = {
 	DEFINE_RES_IRQ_NAMED(BXTWC_ADC_IRQ, "ADC"),
 };
 
+static struct resource usbc_resources[] = {
+	DEFINE_RES_IRQ(BXTWC_USBC_IRQ),
+};
+
 static struct resource charger_resources[] = {
 	DEFINE_RES_IRQ_NAMED(BXTWC_CHGR0_IRQ, "CHARGER"),
 	DEFINE_RES_IRQ_NAMED(BXTWC_CHGR1_IRQ, "CHARGER1"),
@@ -170,6 +176,11 @@ static struct mfd_cell bxt_wc_dev[] = {
 		.resources = thermal_resources,
 	},
 	{
+		.name = "bxt_wcove_usbc",
+		.num_resources = ARRAY_SIZE(usbc_resources),
+		.resources = usbc_resources,
+	},
+	{
 		.name = "bxt_wcove_ext_charger",
 		.num_resources = ARRAY_SIZE(charger_resources),
 		.resources = charger_resources,
-- 
2.8.1

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

* Re: [PATCHv6 1/3] usb: USB Type-C connector class
  2016-08-22 12:05 ` [PATCHv6 1/3] usb: USB Type-C connector class Heikki Krogerus
@ 2016-08-24 14:08   ` Vincent Palatin
  2016-08-25 11:59     ` Heikki Krogerus
  0 siblings, 1 reply; 28+ messages in thread
From: Vincent Palatin @ 2016-08-24 14:08 UTC (permalink / raw)
  To: Heikki Krogerus
  Cc: Greg KH, Guenter Roeck, Oliver Neukum, Felipe Balbi, Bin Gao,
	linux-kernel, linux-usb

Sorry if I'm making redundant comments with previous discussions, I
might have missed a few threads.


On Mon, Aug 22, 2016 at 2:05 PM, Heikki Krogerus
<heikki.krogerus@linux.intel.com> 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 |  199 +++++
>  Documentation/usb/typec.txt                 |  103 +++
>  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                   | 1090 +++++++++++++++++++++++++++
>  include/linux/usb/typec.h                   |  260 +++++++
>  9 files changed, 1673 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..e6179d3
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-class-typec
> @@ -0,0 +1,199 @@
> +USB Type-C port devices (eg. /sys/class/typec/usbc0/)
> +
> +What:          /sys/class/typec/<port>/current_data_role
> +Date:          June 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.

role swapping is sometimes a long operation. maybe we need to say
explicitly whether the 'write' is synchronous and returns when the
swap has succeeded / failed or asynchronous (and requires polling
current_data_role afterwards to know the result ?)


> +
> +               Valid values:
> +               - host
> +               - device

the USB workgroup has settled for DFP/UFP rather than host/device ?


> +
> +What:          /sys/class/typec/<port>/current_power_role
> +Date:          June 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

ditto


> +               USB Power Delivery.
> +
> +               Valid values:
> +               - source
> +               - sink
> +
> +What:          /sys/class/typec/<port>/current_vconn_role
> +Date:          June 2016
> +Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> +               Shows the current VCONN role of the port. This attribute can be
> +               used to request VCONN role swap on the port when the port
> +               supports USB Power Delivery.
> +
> +               Valid values are:
> +               - source
> +               - sink


either we are currently sourcing vconn or not, but even if you are
not, you are probably not a vconn sink either (ie only vconn-powered
accessory are, your usual linux-powered laptop/phone is probably not)


> +
> +What:          /sys/class/typec/<port>/power_operation_mode
> +Date:          June 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
> +               - BC1.2 - Power levels defined in Battery Charging Specification
> +                         v1.2
> +               - 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:          June 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:
> +               - host
> +               - device
> +               - For example "none" to remove preference (anything else except
> +                 "host" or "device")


host/device are not really power roles, source/sink are (or SNK/SRC)


> +
> +What:          /sys/class/typec/<port>/supported_accessory_modes
> +Date:          June 2016
> +Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> +               Lists the Accessory Modes, defined in the USB Type-C
> +               specification, the port supports.


there aren't many modes defined in the type-C spec (most of them are
in other specs or proprietary) overall I don't really what modes my
port supports (and the list might be open-ended, e.g. user space
implementations), I'm really interested in what the partner port
supports.


> +
> +What:          /sys/class/typec/<port>/supported_data_roles
> +Date:          June 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:          June 2016
> +Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> +               Lists the power roles the port is capable of supporting.
> +
> +               Valid values:
> +               - source
> +               - sink
> +
> +What:          /sys/class/typec/<port>/supports_usb_power_delivery
> +Date:          June 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
> +Date:          June 2016
> +Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> +               The attribute is visible only when the partner's type is
> +               "Accessory". The type can be read from its own attribute.
> +
> +               Shows the name of the Accessory Mode. The Accessory Modes are
> +               defined in USB Type-C Specification.
> +
> +What:          /sys/class/typec/<port>-partner/type
> +Date:          June 2016
> +Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> +               Shows the type of the partner. Can be one of the following:
> +               - USB - When the partner is normal USB host/peripheral.
> +               - Charger - When the partner has been identified as dedicated
> +                           charger.
> +               - Alternate Mode - When the partner supports Alternate Modes.
> +               - Accessory - When the partner is one of the accessories with
> +                             specific Accessory Mode defined in USB Type-C
> +                             specification.


where a dock would be classified ?


> +
> +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:          June 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:          June 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
> +
> +
> +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:          June 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.
> +
> +What:          /sys/class/typec/<dev>/<dev>.svid:<svid>/<mode>/description
> +Date:          June 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:          June 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:          June 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..dce5f07
> --- /dev/null
> +++ b/Documentation/usb/typec.txt
> @@ -0,0 +1,103 @@
> +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.


I would need a way to map /sys/bus/usb/ entries with /sys/class/typec/ entries.


> +
> +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 patch to

s/patch/path/


> +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);
> +
> +
> +* 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 320cce8..bc563e3 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -12339,6 +12339,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 8689dcb..f42a3d3 100644
> --- a/drivers/usb/Kconfig
> +++ b/drivers/usb/Kconfig
> @@ -150,6 +150,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 && USB_COMMON && LEDS_TRIGGERS
> diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
> index dca7856..51e381e 100644
> --- a/drivers/usb/Makefile
> +++ b/drivers/usb/Makefile
> @@ -61,3 +61,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..b229fb9
> --- /dev/null
> +++ b/drivers/usb/typec/Kconfig
> @@ -0,0 +1,7 @@
> +
> +menu "USB PD 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..f4c57f0
> --- /dev/null
> +++ b/drivers/usb/typec/typec.c
> @@ -0,0 +1,1090 @@
> +/*
> + * 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)
> +
> +static DEFINE_IDA(typec_index_ida);
> +
> +static struct class typec_class = {
> +       .name = "typec",
> +};
> +
> +static const char * const typec_accessory_modes[] = {
> +       [TYPEC_ACCESSORY_NONE]          = "none",
> +       [TYPEC_ACCESSORY_AUDIO]         = "Audio",
> +       [TYPEC_ACCESSORY_DEBUG]         = "Debug",
> +       [TYPEC_ACCESSORY_DAUDIO]        = "Digital Audio",
> +};
> +
> +static int sysfs_strmatch(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;
> +}
> +
> +/* ------------------------------------------------------------------------- */
> +/* Type-C Partners */
> +
> +static void typec_dev_release(struct device *dev)
> +{
> +}
> +
> +static const char * const typec_partner_types[] = {
> +       [TYPEC_PARTNER_USB]             = "USB",
> +       [TYPEC_PARTNER_CHARGER]         = "Charger",
> +       [TYPEC_PARTNER_ALTMODE]         = "Alternate Mode",
> +       [TYPEC_PARTNER_ACCESSORY]       = "Accessory",
> +};
> +
> +static ssize_t partner_type_show(struct device *dev,
> +                                struct device_attribute *attr, char *buf)
> +{
> +       struct typec_partner *partner = container_of(dev, struct typec_partner,
> +                                                    dev);
> +
> +       return sprintf(buf, "%s\n", typec_partner_types[partner->type]);
> +}
> +
> +static struct device_attribute dev_attr_partner_type = {
> +       .attr = {
> +               .name = "type",
> +               .mode = S_IRUGO,
> +       },
> +       .show = partner_type_show,
> +};
> +
> +static ssize_t
> +partner_accessory_mode_show(struct device *dev, struct device_attribute *attr,
> +                           char *buf)
> +{
> +       struct typec_partner *partner = container_of(dev, struct typec_partner,
> +                                                    dev);
> +
> +       return sprintf(buf, "%s\n", typec_accessory_modes[partner->accessory]);
> +}
> +
> +static struct device_attribute dev_attr_partner_accessory = {
> +       .attr = {
> +               .name = "accessory",
> +               .mode = S_IRUGO,
> +       },
> +       .show = partner_accessory_mode_show,
> +};
> +
> +static struct attribute *typec_partner_attrs[] = {
> +       &dev_attr_partner_accessory.attr,
> +       &dev_attr_partner_type.attr,
> +       NULL
> +};
> +
> +static umode_t
> +partner_is_visible(struct kobject *kobj, struct attribute *attr, int n)
> +{
> +       struct device *dev = container_of(kobj, struct device, kobj);
> +       struct typec_partner *partner = container_of(dev, struct typec_partner,
> +                                                    dev);
> +
> +       if (attr == &dev_attr_partner_accessory.attr &&
> +           partner->type != TYPEC_PARTNER_ACCESSORY)
> +               return 0;
> +
> +       return attr->mode;
> +}
> +
> +static struct attribute_group typec_partner_group = {
> +       .attrs = typec_partner_attrs,
> +       .is_visible = partner_is_visible,
> +};
> +
> +static const struct attribute_group *typec_partner_groups[] = {
> +       &typec_partner_group,
> +       NULL
> +};
> +
> +static struct device_type typec_partner_dev_type = {
> +       .name = "typec_partner_device",
> +       .groups = typec_partner_groups,
> +       .release = typec_dev_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)
> +{
> +       WARN_ON(port->partner->alt_modes);
> +       device_unregister(&port->partner->dev);
> +}
> +
> +/* ------------------------------------------------------------------------- */
> +/* Type-C Cable Plugs */
> +
> +static struct device_type typec_plug_dev_type = {
> +       .name = "typec_plug_device",
> +       .release = typec_dev_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)
> +{
> +       WARN_ON(plug->alt_modes);
> +       device_unregister(&plug->dev);
> +}
> +
> +/* Type-C Cables */
> +
> +static ssize_t
> +cable_active_show(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> +       struct typec_cable *cable = container_of(dev, struct typec_cable, dev);
> +
> +       return sprintf(buf, "%d\n", cable->active);
> +}
> +
> +static struct device_attribute dev_attr_cable_active = {
> +       .attr = {
> +               .name = "active",
> +               .mode = S_IRUGO,
> +       },
> +       .show = cable_active_show,
> +};
> +
> +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
> +cable_plug_type_show(struct device *dev, struct device_attribute *attr,
> +                    char *buf)
> +{
> +       struct typec_cable *cable = container_of(dev, struct typec_cable, dev);
> +
> +       return sprintf(buf, "%s\n", typec_plug_types[cable->type]);
> +}
> +
> +static struct device_attribute dev_attr_plug_type = {
> +       .attr = {
> +               .name = "plug_type",
> +               .mode = S_IRUGO,
> +       },
> +       .show = cable_plug_type_show,
> +};
> +
> +static struct attribute *typec_cable_attrs[] = {
> +       &dev_attr_cable_active.attr,
> +       &dev_attr_plug_type.attr,
> +       NULL
> +};
> +
> +static struct attribute_group typec_cable_group = {
> +       .attrs = typec_cable_attrs,
> +};
> +
> +static const struct attribute_group *typec_cable_groups[] = {
> +       &typec_cable_group,
> +       NULL
> +};
> +
> +static struct device_type typec_cable_dev_type = {
> +       .name = "typec_cable_device",
> +       .groups = typec_cable_groups,
> +       .release = typec_dev_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;
> +
> +       kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
> +
> +       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->partner = NULL;
> +       port->cable = NULL;
> +
> +       port->pwr_opmode = TYPEC_PWR_MODE_USB;
> +
> +       typec_init_roles(port);
> +
> +       kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
> +}
> +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");
> +}
> +EXPORT_SYMBOL(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");
> +}
> +EXPORT_SYMBOL(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");
> +}
> +EXPORT_SYMBOL(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");
> +}
> +EXPORT_SYMBOL(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;
> +       sprintf(dir, "mode%d", mode);


maybe we need a safety check here and verify that `mode` is a single
digit or clip properly the string ?


> +       sysfs_notify(&alt->dev.kobj, dir, "active");
> +}
> +EXPORT_SYMBOL(typec_altmode_update_active);
> +
> +static struct device_type typec_port_dev_type;
> +
> +/*
> + * 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;
> +
> +       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 = S_IRUGO;
> +               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 = S_IRUGO;
> +               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 = S_IWUSR | S_IRUGO;
> +               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 = S_IRUGO;
> +                       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 attibute_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 = container_of(dev,
> +                                                      struct typec_partner,
> +                                                      dev);
> +               p->alt_modes = alt_modes;
> +       } else if (dev->type == &typec_plug_dev_type) {
> +               struct typec_plug *p = container_of(dev, struct 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 = container_of(dev,
> +                                                      struct typec_partner,
> +                                                      dev);
> +               alt_modes = p->alt_modes;
> +               p->alt_modes = NULL;
> +       } else if (dev->type == &typec_plug_dev_type) {
> +               struct typec_plug *p = container_of(dev, struct 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);
> +       enum typec_role 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;
> +       }
> +
> +       ret = sysfs_strmatch(typec_roles, ARRAY_SIZE(typec_roles), buf);
> +       if (ret < 0) {
> +               port->prefer_role = -1;
> +               return size;
> +       }
> +
> +       role = ret;
> +
> +       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);
> +       enum typec_role role;
> +       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_strmatch(typec_data_roles, ARRAY_SIZE(typec_data_roles),
> +                            buf);
> +       if (ret < 0)
> +               return ret;
> +
> +       role = ret;
> +
> +       ret = port->cap->dr_set(port->cap, role);
> +       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);
> +       enum typec_role role;
> +       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_strmatch(typec_roles, ARRAY_SIZE(typec_roles), buf);
> +       if (ret < 0)
> +               return ret;
> +
> +       role = ret;
> +
> +       ret = port->cap->pr_set(port->cap, role);
> +       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 current_vconn_role_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;
> +       }
> +
> +       ret = sysfs_strmatch(typec_roles, ARRAY_SIZE(typec_roles), buf);
> +       if (ret < 0)
> +               return ret;
> +
> +       role = ret;
> +
> +       ret = port->cap->vconn_set(port->cap, role);
> +       if (ret)
> +               return ret;
> +
> +       return size;
> +}
> +
> +static ssize_t current_vconn_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->vconn_role]);
> +}
> +static DEVICE_ATTR_RW(current_vconn_role);
> +
> +static ssize_t supported_accessory_modes_show(struct device *dev,
> +                                             struct device_attribute *attr,
> +                                             char *buf)
> +{
> +       struct typec_port *port = to_typec_port(dev);
> +       enum typec_accessory *accessory;
> +       ssize_t ret = 0;
> +       int i;
> +
> +       if (port->cap->accessory)
> +               for (accessory = port->cap->accessory, i = 0;
> +                    i < port->cap->num_accessory; accessory++, i++)
> +                       ret += sprintf(buf, "%s\n",
> +                                      typec_accessory_modes[*accessory]);
> +       return ret;
> +}
> +static DEVICE_ATTR_RO(supported_accessory_modes);
> +
> +static ssize_t supports_usb_power_delivery_show(struct device *dev,
> +                                               struct device_attribute *attr,
> +                                               char *buf)
> +{
> +       struct typec_port *port = to_typec_port(dev);
> +
> +       return sprintf(buf, "%d\n", port->cap->usb_pd);
> +}
> +static DEVICE_ATTR_RO(supports_usb_power_delivery);
> +
> +static struct attribute *typec_attrs[] = {
> +       &dev_attr_current_power_role.attr,
> +       &dev_attr_current_vconn_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,
> +       NULL,
> +};
> +
> +static const struct attribute_group typec_group = {
> +       .attrs = typec_attrs,
> +};
> +
> +static const struct attribute_group *typec_groups[] = {
> +       &typec_group,
> +       NULL,
> +};
> +
> +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);
> +       }
> +
> +       /* FIXME: a better approach for this */
> +       port->prefer_role = -1;
> +
> +       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)
> +{
> +       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..eda6747
> --- /dev/null
> +++ b/include/linux/usb/typec.h
> @@ -0,0 +1,260 @@
> +
> +#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_partner_type {
> +       TYPEC_PARTNER_USB,
> +       TYPEC_PARTNER_CHARGER,
> +       TYPEC_PARTNER_ALTMODE,
> +       TYPEC_PARTNER_ACCESSORY,
> +};
> +
> +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,
> +       TYPEC_ACCESSORY_DAUDIO,
> +};
> +
> +/*
> + * 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 *);
> +
> +void typec_altmode_update_active(struct typec_altmode *alt, int mode,
> +                                bool active);
> +
> +int typec_register_altmodes(struct device *, struct typec_altmode *);
> +void typec_unregister_altmodes(struct device *);
> +
> +/*
> + * 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;
> +       enum typec_partner_type type;
> +       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
> + * @accessory: Supported Accessory Modes
> + * @num_accessory: Number of supported Accessory Modes
> + * @alt_modes: Alternate Modes the connector supports (null terminated)
> + * @try_role: Set a fixed data role 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;
> +       enum typec_accessory    *accessory;
> +       unsigned int            num_accessory;
> +       struct typec_altmode    *alt_modes;
> +
> +       int                     (*try_role)(const struct typec_capability *,
> +                                           enum typec_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);
> +};
> +
> +/*
> + * 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 *, 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);
> +
> +#endif /* __LINUX_USB_TYPEC_H */
> --
> 2.8.1
>

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

* Re: [PATCHv6 1/3] usb: USB Type-C connector class
  2016-08-24 14:08   ` Vincent Palatin
@ 2016-08-25 11:59     ` Heikki Krogerus
  2016-08-26 13:16       ` Vincent Palatin
  0 siblings, 1 reply; 28+ messages in thread
From: Heikki Krogerus @ 2016-08-25 11:59 UTC (permalink / raw)
  To: Vincent Palatin
  Cc: Greg KH, Guenter Roeck, Oliver Neukum, Felipe Balbi, Bin Gao,
	linux-kernel, linux-usb

Hi,

On Wed, Aug 24, 2016 at 04:08:23PM +0200, Vincent Palatin wrote:
> Sorry if I'm making redundant comments with previous discussions, I
> might have missed a few threads.
> 
> 
> On Mon, Aug 22, 2016 at 2:05 PM, Heikki Krogerus
> <heikki.krogerus@linux.intel.com> 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 |  199 +++++
> >  Documentation/usb/typec.txt                 |  103 +++
> >  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                   | 1090 +++++++++++++++++++++++++++
> >  include/linux/usb/typec.h                   |  260 +++++++
> >  9 files changed, 1673 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..e6179d3
> > --- /dev/null
> > +++ b/Documentation/ABI/testing/sysfs-class-typec
> > @@ -0,0 +1,199 @@
> > +USB Type-C port devices (eg. /sys/class/typec/usbc0/)
> > +
> > +What:          /sys/class/typec/<port>/current_data_role
> > +Date:          June 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.
> 
> role swapping is sometimes a long operation. maybe we need to say
> explicitly whether the 'write' is synchronous and returns when the
> swap has succeeded / failed or asynchronous (and requires polling
> current_data_role afterwards to know the result ?)

OK.

> > +
> > +               Valid values:
> > +               - host
> > +               - device
> 
> the USB workgroup has settled for DFP/UFP rather than host/device ?

(I don't think the workgroup has settled on anything.)

I already proposed DFP/UFP, but it did not fly. The naming really does
not need to reflect the spec exactly, especially since there is no
guarantee they will not change the names in future versions of the
spec. "host/device", "source/sink" are completely understandable,
unlike DRP/UFP.

> 
> > +
> > +What:          /sys/class/typec/<port>/current_power_role
> > +Date:          June 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
> 
> ditto
> 
> 
> > +               USB Power Delivery.
> > +
> > +               Valid values:
> > +               - source
> > +               - sink
> > +
> > +What:          /sys/class/typec/<port>/current_vconn_role
> > +Date:          June 2016
> > +Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
> > +Description:
> > +               Shows the current VCONN role of the port. This attribute can be
> > +               used to request VCONN role swap on the port when the port
> > +               supports USB Power Delivery.
> > +
> > +               Valid values are:
> > +               - source
> > +               - sink
> 
> 
> either we are currently sourcing vconn or not, but even if you are
> not, you are probably not a vconn sink either (ie only vconn-powered
> accessory are, your usual linux-powered laptop/phone is probably not)

It's not relevant to know whether the vconn is being actually used or
not here. I'm not sure what's your point?

And vconn does not only supply vonn-powered accessories, but
powered cables as well.

> > +
> > +What:          /sys/class/typec/<port>/power_operation_mode
> > +Date:          June 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
> > +               - BC1.2 - Power levels defined in Battery Charging Specification
> > +                         v1.2
> > +               - 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:          June 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:
> > +               - host
> > +               - device
> > +               - For example "none" to remove preference (anything else except
> > +                 "host" or "device")
> 
> 
> host/device are not really power roles, source/sink are (or SNK/SRC)

Try.SRC/SNK are primarily designed for systems that don't implement
USB PD, and therefore source = host and sink = device. But even when
USB PD is supported, the initial data role will be host in case of
source and device in case of sink.

And as said before, the naming does not need to match that of the spec
here.

> > +
> > +What:          /sys/class/typec/<port>/supported_accessory_modes
> > +Date:          June 2016
> > +Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
> > +Description:
> > +               Lists the Accessory Modes, defined in the USB Type-C
> > +               specification, the port supports.
> 
> 
> there aren't many modes defined in the type-C spec (most of them are
> in other specs or proprietary) overall I don't really what modes my
> port supports (and the list might be open-ended, e.g. user space
> implementations), I'm really interested in what the partner port
> supports.

I think you are mixing Accessory Modes and Alternate Modes (most
likely because of the vconn-powered accessory concept). Note that
vconn-powered accessory is actually just a sink that implements an
alternate mode.

You can have vendor specific alternate modes, but the accessory modes
are what the spec defines, so Audio and Debug (and Digital Audio in
the future).

> 
> > +
> > +What:          /sys/class/typec/<port>/supported_data_roles
> > +Date:          June 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:          June 2016
> > +Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
> > +Description:
> > +               Lists the power roles the port is capable of supporting.
> > +
> > +               Valid values:
> > +               - source
> > +               - sink
> > +
> > +What:          /sys/class/typec/<port>/supports_usb_power_delivery
> > +Date:          June 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
> > +Date:          June 2016
> > +Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
> > +Description:
> > +               The attribute is visible only when the partner's type is
> > +               "Accessory". The type can be read from its own attribute.
> > +
> > +               Shows the name of the Accessory Mode. The Accessory Modes are
> > +               defined in USB Type-C Specification.
> > +
> > +What:          /sys/class/typec/<port>-partner/type
> > +Date:          June 2016
> > +Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
> > +Description:
> > +               Shows the type of the partner. Can be one of the following:
> > +               - USB - When the partner is normal USB host/peripheral.
> > +               - Charger - When the partner has been identified as dedicated
> > +                           charger.
> > +               - Alternate Mode - When the partner supports Alternate Modes.
> > +               - Accessory - When the partner is one of the accessories with
> > +                             specific Accessory Mode defined in USB Type-C
> > +                             specification.
> 
> 
> where a dock would be classified ?

A dock is just USB PD capable device with a bunch of alternate modes
that is attached to the port. There is no specific identifier for a
"dock".

> > +
> > +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:          June 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:          June 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
> > +
> > +
> > +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:          June 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.
> > +
> > +What:          /sys/class/typec/<dev>/<dev>.svid:<svid>/<mode>/description
> > +Date:          June 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:          June 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:          June 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..dce5f07
> > --- /dev/null
> > +++ b/Documentation/usb/typec.txt
> > @@ -0,0 +1,103 @@
> > +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.
> 
> 
> I would need a way to map /sys/bus/usb/ entries with /sys/class/typec/ entries.

This needs planning, however this should not effect the class driver
at this point. The linking needs to happen in the drivers registering
the ports at least in the beginning. I fear there isn't a single
method that could be used on all types of platforms.

With ACPI we can find the port with the companion ACPI device for
the port itself. I don't know is this documented anywhere, but the
ACPI device object of the port is provided as a child for the typec
devices on boards that I've seen so far. I have no idea how the
linking even could be done in DT.

> > +
> > +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 patch to
> 
> s/patch/path/

OK.

<snip>

> > +/*
> > + * 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;
> > +       sprintf(dir, "mode%d", mode);
> 
> 
> maybe we need a safety check here and verify that `mode` is a single
> digit or clip properly the string ?

Sure.

> 
> > +       sysfs_notify(&alt->dev.kobj, dir, "active");
> > +}
> > +EXPORT_SYMBOL(typec_altmode_update_active);


Thanks for the review,

-- 
heikki

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

* Re: [PATCHv6 1/3] usb: USB Type-C connector class
  2016-08-25 11:59     ` Heikki Krogerus
@ 2016-08-26 13:16       ` Vincent Palatin
  2016-08-26 14:07         ` Heikki Krogerus
  0 siblings, 1 reply; 28+ messages in thread
From: Vincent Palatin @ 2016-08-26 13:16 UTC (permalink / raw)
  To: Heikki Krogerus
  Cc: Greg KH, Guenter Roeck, Oliver Neukum, Felipe Balbi, Bin Gao,
	linux-kernel, linux-usb

On Thu, Aug 25, 2016 at 1:59 PM, Heikki Krogerus
<heikki.krogerus@linux.intel.com> wrote:
> Hi,
>
> On Wed, Aug 24, 2016 at 04:08:23PM +0200, Vincent Palatin wrote:
>> Sorry if I'm making redundant comments with previous discussions, I
>> might have missed a few threads.
>>
>>
>> On Mon, Aug 22, 2016 at 2:05 PM, Heikki Krogerus
>> <heikki.krogerus@linux.intel.com> 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 |  199 +++++
>> >  Documentation/usb/typec.txt                 |  103 +++
>> >  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                   | 1090 +++++++++++++++++++++++++++
>> >  include/linux/usb/typec.h                   |  260 +++++++
>> >  9 files changed, 1673 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..e6179d3
>> > --- /dev/null
>> > +++ b/Documentation/ABI/testing/sysfs-class-typec
>> > @@ -0,0 +1,199 @@
>> > +USB Type-C port devices (eg. /sys/class/typec/usbc0/)
>> > +
>> > +What:          /sys/class/typec/<port>/current_data_role
>> > +Date:          June 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.
>>
>> role swapping is sometimes a long operation. maybe we need to say
>> explicitly whether the 'write' is synchronous and returns when the
>> swap has succeeded / failed or asynchronous (and requires polling
>> current_data_role afterwards to know the result ?)
>
> OK.
>
>> > +
>> > +               Valid values:
>> > +               - host
>> > +               - device
>>
>> the USB workgroup has settled for DFP/UFP rather than host/device ?
>
> (I don't think the workgroup has settled on anything.)
>
> I already proposed DFP/UFP, but it did not fly. The naming really does
> not need to reflect the spec exactly, especially since there is no
> guarantee they will not change the names in future versions of the
> spec. "host/device", "source/sink" are completely understandable,
> unlike DRP/UFP.

OK.

>>
>> > +
>> > +What:          /sys/class/typec/<port>/current_power_role
>> > +Date:          June 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
>>
>> ditto
>>
>>
>> > +               USB Power Delivery.
>> > +
>> > +               Valid values:
>> > +               - source
>> > +               - sink
>> > +
>> > +What:          /sys/class/typec/<port>/current_vconn_role
>> > +Date:          June 2016
>> > +Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
>> > +Description:
>> > +               Shows the current VCONN role of the port. This attribute can be
>> > +               used to request VCONN role swap on the port when the port
>> > +               supports USB Power Delivery.
>> > +
>> > +               Valid values are:
>> > +               - source
>> > +               - sink
>>
>>
>> either we are currently sourcing vconn or not, but even if you are
>> not, you are probably not a vconn sink either (ie only vconn-powered
>> accessory are, your usual linux-powered laptop/phone is probably not)
>
> It's not relevant to know whether the vconn is being actually used or
> not here. I'm not sure what's your point?


My point was: saying we are a VCONN "sink" just because we are not
currently sourcing vconn is usually not true.


>
> And vconn does not only supply vonn-powered accessories, but
> powered cables as well.
>
>> > +
>> > +What:          /sys/class/typec/<port>/power_operation_mode
>> > +Date:          June 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
>> > +               - BC1.2 - Power levels defined in Battery Charging Specification
>> > +                         v1.2
>> > +               - 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:          June 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:
>> > +               - host
>> > +               - device
>> > +               - For example "none" to remove preference (anything else except
>> > +                 "host" or "device")
>>
>>
>> host/device are not really power roles, source/sink are (or SNK/SRC)
>
> Try.SRC/SNK are primarily designed for systems that don't implement
> USB PD, and therefore source = host and sink = device. But even when
> USB PD is supported, the initial data role will be host in case of
> source and device in case of sink.
>
> And as said before, the naming does not need to match that of the spec
> here.

sure it does not, I just found it confusing since we are referring
only to the power role.

>
>> > +
>> > +What:          /sys/class/typec/<port>/supported_accessory_modes
>> > +Date:          June 2016
>> > +Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
>> > +Description:
>> > +               Lists the Accessory Modes, defined in the USB Type-C
>> > +               specification, the port supports.
>>
>>
>> there aren't many modes defined in the type-C spec (most of them are
>> in other specs or proprietary) overall I don't really what modes my
>> port supports (and the list might be open-ended, e.g. user space
>> implementations), I'm really interested in what the partner port
>> supports.
>
> I think you are mixing Accessory Modes and Alternate Modes (most
> likely because of the vconn-powered accessory concept).

Indeed I was.

> Note that
> vconn-powered accessory is actually just a sink that implements an
> alternate mode.
>
> You can have vendor specific alternate modes, but the accessory modes
> are what the spec defines, so Audio and Debug (and Digital Audio in
> the future).
>
>>
>> > +
>> > +What:          /sys/class/typec/<port>/supported_data_roles
>> > +Date:          June 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:          June 2016
>> > +Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
>> > +Description:
>> > +               Lists the power roles the port is capable of supporting.
>> > +
>> > +               Valid values:
>> > +               - source
>> > +               - sink
>> > +
>> > +What:          /sys/class/typec/<port>/supports_usb_power_delivery
>> > +Date:          June 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
>> > +Date:          June 2016
>> > +Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
>> > +Description:
>> > +               The attribute is visible only when the partner's type is
>> > +               "Accessory". The type can be read from its own attribute.
>> > +
>> > +               Shows the name of the Accessory Mode. The Accessory Modes are
>> > +               defined in USB Type-C Specification.
>> > +
>> > +What:          /sys/class/typec/<port>-partner/type
>> > +Date:          June 2016
>> > +Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
>> > +Description:
>> > +               Shows the type of the partner. Can be one of the following:
>> > +               - USB - When the partner is normal USB host/peripheral.
>> > +               - Charger - When the partner has been identified as dedicated
>> > +                           charger.
>> > +               - Alternate Mode - When the partner supports Alternate Modes.
>> > +               - Accessory - When the partner is one of the accessories with
>> > +                             specific Accessory Mode defined in USB Type-C
>> > +                             specification.
>>
>>
>> where a dock would be classified ?
>
> A dock is just USB PD capable device with a bunch of alternate modes
> that is attached to the port. There is no specific identifier for a
> "dock".

My remark was a bit too stern,
I meant a dock might be 'USB' 'Charger' 'Alternate Mode' , all at the
same time or alternately depending what you plug in.
I don't really see those types as mutually exclusive.


>
>> > +
>> > +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:          June 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:          June 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
>> > +
>> > +
>> > +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:          June 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.
>> > +
>> > +What:          /sys/class/typec/<dev>/<dev>.svid:<svid>/<mode>/description
>> > +Date:          June 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:          June 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:          June 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..dce5f07
>> > --- /dev/null
>> > +++ b/Documentation/usb/typec.txt
>> > @@ -0,0 +1,103 @@
>> > +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.
>>
>>
>> I would need a way to map /sys/bus/usb/ entries with /sys/class/typec/ entries.
>
> This needs planning, however this should not effect the class driver
> at this point. The linking needs to happen in the drivers registering
> the ports at least in the beginning. I fear there isn't a single
> method that could be used on all types of platforms.
>
> With ACPI we can find the port with the companion ACPI device for
> the port itself. I don't know is this documented anywhere, but the
> ACPI device object of the port is provided as a child for the typec
> devices on boards that I've seen so far. I have no idea how the
> linking even could be done in DT.
>
>> > +
>> > +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 patch to
>>
>> s/patch/path/
>
> OK.
>
> <snip>
>
>> > +/*
>> > + * 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;
>> > +       sprintf(dir, "mode%d", mode);
>>
>>
>> maybe we need a safety check here and verify that `mode` is a single
>> digit or clip properly the string ?
>
> Sure.
>
>>
>> > +       sysfs_notify(&alt->dev.kobj, dir, "active");
>> > +}
>> > +EXPORT_SYMBOL(typec_altmode_update_active);
>
>
> Thanks for the review,
>
> --
> heikki

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

* Re: [PATCHv6 1/3] usb: USB Type-C connector class
  2016-08-26 13:16       ` Vincent Palatin
@ 2016-08-26 14:07         ` Heikki Krogerus
  2016-08-29 13:04           ` Guenter Roeck
  0 siblings, 1 reply; 28+ messages in thread
From: Heikki Krogerus @ 2016-08-26 14:07 UTC (permalink / raw)
  To: Vincent Palatin
  Cc: Greg KH, Guenter Roeck, Oliver Neukum, Felipe Balbi, Bin Gao,
	linux-kernel, linux-usb

Hi Vincent,

On Fri, Aug 26, 2016 at 03:16:16PM +0200, Vincent Palatin wrote:
> >> > +What:          /sys/class/typec/<port>/current_vconn_role
> >> > +Date:          June 2016
> >> > +Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
> >> > +Description:
> >> > +               Shows the current VCONN role of the port. This attribute can be
> >> > +               used to request VCONN role swap on the port when the port
> >> > +               supports USB Power Delivery.
> >> > +
> >> > +               Valid values are:
> >> > +               - source
> >> > +               - sink
> >>
> >>
> >> either we are currently sourcing vconn or not, but even if you are
> >> not, you are probably not a vconn sink either (ie only vconn-powered
> >> accessory are, your usual linux-powered laptop/phone is probably not)
> >
> > It's not relevant to know whether the vconn is being actually used or
> > not here. I'm not sure what's your point?
> 
> 
> My point was: saying we are a VCONN "sink" just because we are not
> currently sourcing vconn is usually not true.

OK, I understand your point now. You are correct. I think we need to
change this attribute and call it "vconn_source" that reports "1" or
"0".

I'll change that and send one more version of these on Monday
(hopefully the last one) unless somebody disagrees.

> >> > +What:          /sys/class/typec/<port>-partner/type
> >> > +Date:          June 2016
> >> > +Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
> >> > +Description:
> >> > +               Shows the type of the partner. Can be one of the following:
> >> > +               - USB - When the partner is normal USB host/peripheral.
> >> > +               - Charger - When the partner has been identified as dedicated
> >> > +                           charger.
> >> > +               - Alternate Mode - When the partner supports Alternate Modes.
> >> > +               - Accessory - When the partner is one of the accessories with
> >> > +                             specific Accessory Mode defined in USB Type-C
> >> > +                             specification.
> >>
> >>
> >> where a dock would be classified ?
> >
> > A dock is just USB PD capable device with a bunch of alternate modes
> > that is attached to the port. There is no specific identifier for a
> > "dock".
> 
> My remark was a bit too stern,
> I meant a dock might be 'USB' 'Charger' 'Alternate Mode' , all at the
> same time or alternately depending what you plug in.
> I don't really see those types as mutually exclusive.

So USB type means the partner does not have alternate modes (I'll
clear that in the documentation), Charger is a dedicated charger and
therefore can not be anything else (no USB, no alternate modes).

To answer your original question, a dock would be reported as
Alternate Mode.


Thanks,

-- 
heikki

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

* Re: [PATCHv6 1/3] usb: USB Type-C connector class
  2016-08-26 14:07         ` Heikki Krogerus
@ 2016-08-29 13:04           ` Guenter Roeck
  2016-08-29 13:43             ` Heikki Krogerus
  0 siblings, 1 reply; 28+ messages in thread
From: Guenter Roeck @ 2016-08-29 13:04 UTC (permalink / raw)
  To: Heikki Krogerus, Vincent Palatin
  Cc: Greg KH, Oliver Neukum, Felipe Balbi, Bin Gao, linux-kernel, linux-usb

Heikki,

On 08/26/2016 07:07 AM, Heikki Krogerus wrote:
>
>>>>> +What:          /sys/class/typec/<port>-partner/type
>>>>> +Date:          June 2016
>>>>> +Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
>>>>> +Description:
>>>>> +               Shows the type of the partner. Can be one of the following:
>>>>> +               - USB - When the partner is normal USB host/peripheral.
>>>>> +               - Charger - When the partner has been identified as dedicated
>>>>> +                           charger.
>>>>> +               - Alternate Mode - When the partner supports Alternate Modes.
>>>>> +               - Accessory - When the partner is one of the accessories with
>>>>> +                             specific Accessory Mode defined in USB Type-C
>>>>> +                             specification.
>>>>
>>>>
>>>> where a dock would be classified ?
>>>
>>> A dock is just USB PD capable device with a bunch of alternate modes
>>> that is attached to the port. There is no specific identifier for a
>>> "dock".
>>
>> My remark was a bit too stern,
>> I meant a dock might be 'USB' 'Charger' 'Alternate Mode' , all at the
>> same time or alternately depending what you plug in.
>> I don't really see those types as mutually exclusive.
>
> So USB type means the partner does not have alternate modes (I'll
> clear that in the documentation), Charger is a dedicated charger and
> therefore can not be anything else (no USB, no alternate modes).
>

This is probably the most difficult attribute to support.

Many PD capable chargers support alternate modes (for firmware upgrades).
As I mentioned earlier, it is difficult to match reported Type-C partner
types (or really anything reported in the SVDM Identity command)
to the above types.

Does it really make sense to deviate that much from the Type-C specification ?
I can understand why you hesitate to use DFP / UFP, as those terms are
really hard to understand for the non-initiated. However, here it is really
difficult to even determine which value to set. The best I can come up with is

- Not PD capable. Report USB (obviously includes non-PD capable chargers)
- PD capable, supports alternate modes. Report as Alternate Mode (including
   PD chargers supporting alternate modes)
- PD capable, does not support alternate modes. Report as Accessory if
   connected as accessory, as charger if we the port is connected as sink,
   USB otherwise

Overall this is quite vague and, especially for chargers, most of the time
misses the point.

I would really prefer if we could stay closer to the specification in this
case, and not try to merge multiple orthogonal attributes into one.

Thanks,
Guenter

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

* Re: [PATCHv6 1/3] usb: USB Type-C connector class
  2016-08-29 13:04           ` Guenter Roeck
@ 2016-08-29 13:43             ` Heikki Krogerus
  2016-08-29 14:07               ` Heikki Krogerus
  0 siblings, 1 reply; 28+ messages in thread
From: Heikki Krogerus @ 2016-08-29 13:43 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Vincent Palatin, Greg KH, Oliver Neukum, Felipe Balbi, Bin Gao,
	linux-kernel, linux-usb

On Mon, Aug 29, 2016 at 06:04:52AM -0700, Guenter Roeck wrote:
> Heikki,
> 
> On 08/26/2016 07:07 AM, Heikki Krogerus wrote:
> > 
> > > > > > +What:          /sys/class/typec/<port>-partner/type
> > > > > > +Date:          June 2016
> > > > > > +Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
> > > > > > +Description:
> > > > > > +               Shows the type of the partner. Can be one of the following:
> > > > > > +               - USB - When the partner is normal USB host/peripheral.
> > > > > > +               - Charger - When the partner has been identified as dedicated
> > > > > > +                           charger.
> > > > > > +               - Alternate Mode - When the partner supports Alternate Modes.
> > > > > > +               - Accessory - When the partner is one of the accessories with
> > > > > > +                             specific Accessory Mode defined in USB Type-C
> > > > > > +                             specification.
> > > > > 
> > > > > 
> > > > > where a dock would be classified ?
> > > > 
> > > > A dock is just USB PD capable device with a bunch of alternate modes
> > > > that is attached to the port. There is no specific identifier for a
> > > > "dock".
> > > 
> > > My remark was a bit too stern,
> > > I meant a dock might be 'USB' 'Charger' 'Alternate Mode' , all at the
> > > same time or alternately depending what you plug in.
> > > I don't really see those types as mutually exclusive.
> > 
> > So USB type means the partner does not have alternate modes (I'll
> > clear that in the documentation), Charger is a dedicated charger and
> > therefore can not be anything else (no USB, no alternate modes).
> > 
> 
> This is probably the most difficult attribute to support.
> 
> Many PD capable chargers support alternate modes (for firmware upgrades).
> As I mentioned earlier, it is difficult to match reported Type-C partner
> types (or really anything reported in the SVDM Identity command)
> to the above types.
> 
> Does it really make sense to deviate that much from the Type-C specification ?
> I can understand why you hesitate to use DFP / UFP, as those terms are
> really hard to understand for the non-initiated. However, here it is really
> difficult to even determine which value to set. The best I can come up with is
> 
> - Not PD capable. Report USB (obviously includes non-PD capable chargers)
> - PD capable, supports alternate modes. Report as Alternate Mode (including
>   PD chargers supporting alternate modes)
> - PD capable, does not support alternate modes. Report as Accessory if
>   connected as accessory, as charger if we the port is connected as sink,
>   USB otherwise
> 
> Overall this is quite vague and, especially for chargers, most of the time
> misses the point.
> 
> I would really prefer if we could stay closer to the specification in this
> case, and not try to merge multiple orthogonal attributes into one.

OK. So what would you propose?


Thanks,

-- 
heikki

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

* Re: [PATCHv6 1/3] usb: USB Type-C connector class
  2016-08-29 13:43             ` Heikki Krogerus
@ 2016-08-29 14:07               ` Heikki Krogerus
  2016-08-29 18:50                 ` Guenter Roeck
  0 siblings, 1 reply; 28+ messages in thread
From: Heikki Krogerus @ 2016-08-29 14:07 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Vincent Palatin, Greg KH, Oliver Neukum, Felipe Balbi, Bin Gao,
	linux-kernel, linux-usb

Hi Guenter,

> > Overall this is quite vague and, especially for chargers, most of the time
> > misses the point.
> > 
> > I would really prefer if we could stay closer to the specification in this
> > case, and not try to merge multiple orthogonal attributes into one.
> 
> OK. So what would you propose?

I'm actually only conserned about the accessory case, as there we are
really not a source/sink/DRP, nor are we DPF/UFP/DRD. Should we use
this attribute to only express if the type of the partner is "normal"
or an accessory?


Thanks,

-- 
heikki

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

* Re: [PATCHv6 1/3] usb: USB Type-C connector class
  2016-08-29 14:07               ` Heikki Krogerus
@ 2016-08-29 18:50                 ` Guenter Roeck
  2016-08-30  8:22                   ` Heikki Krogerus
  0 siblings, 1 reply; 28+ messages in thread
From: Guenter Roeck @ 2016-08-29 18:50 UTC (permalink / raw)
  To: Heikki Krogerus
  Cc: Vincent Palatin, Greg KH, Oliver Neukum, Felipe Balbi, Bin Gao,
	linux-kernel, linux-usb

Hello Heikki,

On Mon, Aug 29, 2016 at 05:07:39PM +0300, Heikki Krogerus wrote:
> Hi Guenter,
> 
> > > Overall this is quite vague and, especially for chargers, most of the time
> > > misses the point.
> > > 
> > > I would really prefer if we could stay closer to the specification in this
> > > case, and not try to merge multiple orthogonal attributes into one.
> > 
> > OK. So what would you propose?
> 
> I'm actually only conserned about the accessory case, as there we are
> really not a source/sink/DRP, nor are we DPF/UFP/DRD. Should we use
> this attribute to only express if the type of the partner is "normal"
> or an accessory?
> 

We currently have three attributes to cover accessory modes.

supported_accessory_modes
	Lists the Accessory Modes, defined in the USB Type-C
	specification, the port supports.

	[ This is a bit vague. I think we should list the actual strings.
	  The modes are called "Audio Adapter Accessory Mode" and "Debug
	  Accessory Mode", yet the reported text is "Audio" and "Debug".
	  Also, "Digital Audio" isn't supported as of specification revision
	  1.2. So the strings doesn't exactly follow the specification. ]

accessory
	Shows the name of the Accessory Mode. The Accessory Modes are
	defined in USB Type-C Specification.

type
	Shows the type of the partner.

One of the possible accessory modes is TYPEC_ACCESSORY_NONE.

If you are only interested in accessory mode support, maybe we don't need
the 'type' attribute at all. We could make the 'accessory' attribute always
visible and display one of "none", "Audio", "Debug", or "Digital Audio".
It might also make sense to rename the attribute to "accessory_mode".

On a side note, while looking into this, I noticed the following:

+       if (port->cap->accessory)
+               for (accessory = port->cap->accessory, i = 0;
+                    i < port->cap->num_accessory; accessory++, i++)
+                       ret += sprintf(buf, "%s\n",
+                                      typec_accessory_modes[*accessory]);

This means the list of supported accessories always starts with ", ".

Thanks,
Guenter

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

* Re: [PATCHv6 1/3] usb: USB Type-C connector class
  2016-08-29 18:50                 ` Guenter Roeck
@ 2016-08-30  8:22                   ` Heikki Krogerus
  2016-08-30 15:28                     ` Guenter Roeck
  2016-08-30 17:00                     ` Guenter Roeck
  0 siblings, 2 replies; 28+ messages in thread
From: Heikki Krogerus @ 2016-08-30  8:22 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Vincent Palatin, Greg KH, Oliver Neukum, Felipe Balbi, Bin Gao,
	linux-kernel, linux-usb

Hi Guenter,

On Mon, Aug 29, 2016 at 11:50:49AM -0700, Guenter Roeck wrote:
> Hello Heikki,
> 
> On Mon, Aug 29, 2016 at 05:07:39PM +0300, Heikki Krogerus wrote:
> > Hi Guenter,
> > 
> > > > Overall this is quite vague and, especially for chargers, most of the time
> > > > misses the point.
> > > > 
> > > > I would really prefer if we could stay closer to the specification in this
> > > > case, and not try to merge multiple orthogonal attributes into one.
> > > 
> > > OK. So what would you propose?
> > 
> > I'm actually only conserned about the accessory case, as there we are
> > really not a source/sink/DRP, nor are we DPF/UFP/DRD. Should we use
> > this attribute to only express if the type of the partner is "normal"
> > or an accessory?
> > 
> 
> We currently have three attributes to cover accessory modes.
> 
> supported_accessory_modes
> 	Lists the Accessory Modes, defined in the USB Type-C
> 	specification, the port supports.
> 
> 	[ This is a bit vague. I think we should list the actual strings.
> 	  The modes are called "Audio Adapter Accessory Mode" and "Debug
> 	  Accessory Mode", yet the reported text is "Audio" and "Debug".
> 	  Also, "Digital Audio" isn't supported as of specification revision
> 	  1.2. So the strings doesn't exactly follow the specification. ]

I'm fine if we want to use more precise strings.

> accessory
> 	Shows the name of the Accessory Mode. The Accessory Modes are
> 	defined in USB Type-C Specification.
> 
> type
> 	Shows the type of the partner.
> 
> One of the possible accessory modes is TYPEC_ACCESSORY_NONE.
> 
> If you are only interested in accessory mode support, maybe we don't need
> the 'type' attribute at all. We could make the 'accessory' attribute always
> visible and display one of "none", "Audio", "Debug", or "Digital Audio".
> It might also make sense to rename the attribute to "accessory_mode".

That works for me.

How about if I add the "supports_usb_power_delivery" attribute for the
partners instead to give some details about them. Any objections?

> On a side note, while looking into this, I noticed the following:
> 
> +       if (port->cap->accessory)
> +               for (accessory = port->cap->accessory, i = 0;
> +                    i < port->cap->num_accessory; accessory++, i++)
> +                       ret += sprintf(buf, "%s\n",
> +                                      typec_accessory_modes[*accessory]);
> 
> This means the list of supported accessories always starts with ", ".

Where does it print ", "?

I'm not sure what is wrong here, but I'll update this code in any
case. I'll change the accessory member in typec_capability into fixed
size array to make it easier to deal with for now.


Thanks,

-- 
heikki

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

* Re: [PATCHv6 1/3] usb: USB Type-C connector class
  2016-08-30  8:22                   ` Heikki Krogerus
@ 2016-08-30 15:28                     ` Guenter Roeck
  2016-08-30 17:00                     ` Guenter Roeck
  1 sibling, 0 replies; 28+ messages in thread
From: Guenter Roeck @ 2016-08-30 15:28 UTC (permalink / raw)
  To: Heikki Krogerus
  Cc: Vincent Palatin, Greg KH, Oliver Neukum, Felipe Balbi, Bin Gao,
	linux-kernel, linux-usb

Hello Heikki,

On Tue, Aug 30, 2016 at 11:22:27AM +0300, Heikki Krogerus wrote:
> > 
> > If you are only interested in accessory mode support, maybe we don't need
> > the 'type' attribute at all. We could make the 'accessory' attribute always
> > visible and display one of "none", "Audio", "Debug", or "Digital Audio".
> > It might also make sense to rename the attribute to "accessory_mode".
> 
> That works for me.
> 
> How about if I add the "supports_usb_power_delivery" attribute for the
> partners instead to give some details about them. Any objections?
> 
At first glance, the attribute name looks a bit awkward. Let me look
into the specification to see what might make sense to report. On top of my
head, I don't recall if we are able to report this for a dock which isn't
currently connected to power.

> > On a side note, while looking into this, I noticed the following:
> > 
> > +       if (port->cap->accessory)
> > +               for (accessory = port->cap->accessory, i = 0;
> > +                    i < port->cap->num_accessory; accessory++, i++)
> > +                       ret += sprintf(buf, "%s\n",
> > +                                      typec_accessory_modes[*accessory]);
> > 
> > This means the list of supported accessories always starts with ", ".
> 
> Where does it print ", "?
> 
> I'm not sure what is wrong here, but I'll update this code in any

Nothing. Looks like I lost my ability to read code. Somehow the ',' above made
it into the string. There is some inconsistency in the output when compared to
the other "supported" attributes, though. Here the supported modes are printed
in consecutive lines; elsewhere they are printed in a single line with ',' as
separator.

Thanks,
Guenter

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

* Re: [PATCHv6 1/3] usb: USB Type-C connector class
  2016-08-30  8:22                   ` Heikki Krogerus
  2016-08-30 15:28                     ` Guenter Roeck
@ 2016-08-30 17:00                     ` Guenter Roeck
  1 sibling, 0 replies; 28+ messages in thread
From: Guenter Roeck @ 2016-08-30 17:00 UTC (permalink / raw)
  To: Heikki Krogerus
  Cc: Vincent Palatin, Greg KH, Oliver Neukum, Felipe Balbi, Bin Gao,
	linux-kernel, linux-usb

Heikki,

On Tue, Aug 30, 2016 at 11:22:27AM +0300, Heikki Krogerus wrote:
> 
> How about if I add the "supports_usb_power_delivery" attribute for the
> partners instead to give some details about them. Any objections?
> 
After looking into the code again, I assume the idea is to have the existing
supports_usb_power_delivery attribute report if the local port supports the
PD, and to have the partner attribute report if the partner supports the PD
protocol. In other words, it would report the value of usb_pd in struct
typec_partner.

If so, I am ok with it. You might actually consider adding the same attribute
to the cable attributes as well.

Thanks,
Guenter

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

* Re: [PATCHv6 1/3] usb: USB Type-C connector class
  2016-08-31 11:09               ` Heikki Krogerus
@ 2016-08-31 13:18                 ` Guenter Roeck
  0 siblings, 0 replies; 28+ messages in thread
From: Guenter Roeck @ 2016-08-31 13:18 UTC (permalink / raw)
  To: Heikki Krogerus
  Cc: Oliver Neukum, Vincent Palatin, Bin Gao, Felipe Balbi, Greg KH,
	linux-kernel, linux-usb

On 08/31/2016 04:09 AM, Heikki Krogerus wrote:
> Hi guys,
>
> On Tue, Aug 30, 2016 at 06:47:41AM -0700, Guenter Roeck wrote:
>> On 08/30/2016 06:11 AM, Heikki Krogerus wrote:
>>> Hi,
>>>
>>> On Tue, Aug 30, 2016 at 02:49:50PM +0300, Heikki Krogerus wrote:
>>>> On Tue, Aug 30, 2016 at 01:16:46PM +0200, Oliver Neukum wrote:
>>>>> Error reporting does not require a synchronous operation. Reporting
>>>>> it in the next read() or write() and making it pollable is perfectly
>>>>> viable. It just must not be silently dropped.
>>>>
>>>> OK, I think I got it. I need to document that. I'll also add
>>>> get_pr/dr/vconn hooks to the API for getting the status.
>>>
>>> Would the attached diff do the trick? It also includes the other
>>> suggestions from Guenter.
>>>
>>
>> It is not at all what I meant or asked for :-(. I'll have a closer
>> look into the latest patch set later today.
>
> An other attempt. This one has just the suggestions from you Guenter
> (including the supports_usb_power_delivery attibute for cables as
> well), and update to the ABI document.
>
> Let me know if it's OK.
>

Yes, much better.

Thanks,
Guenter

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

* Re: [PATCHv6 1/3] usb: USB Type-C connector class
  2016-08-30 13:47             ` Guenter Roeck
@ 2016-08-31 11:09               ` Heikki Krogerus
  2016-08-31 13:18                 ` Guenter Roeck
  0 siblings, 1 reply; 28+ messages in thread
From: Heikki Krogerus @ 2016-08-31 11:09 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Oliver Neukum, Vincent Palatin, Bin Gao, Felipe Balbi, Greg KH,
	linux-kernel, linux-usb

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

Hi guys,

On Tue, Aug 30, 2016 at 06:47:41AM -0700, Guenter Roeck wrote:
> On 08/30/2016 06:11 AM, Heikki Krogerus wrote:
> > Hi,
> > 
> > On Tue, Aug 30, 2016 at 02:49:50PM +0300, Heikki Krogerus wrote:
> > > On Tue, Aug 30, 2016 at 01:16:46PM +0200, Oliver Neukum wrote:
> > > > Error reporting does not require a synchronous operation. Reporting
> > > > it in the next read() or write() and making it pollable is perfectly
> > > > viable. It just must not be silently dropped.
> > > 
> > > OK, I think I got it. I need to document that. I'll also add
> > > get_pr/dr/vconn hooks to the API for getting the status.
> > 
> > Would the attached diff do the trick? It also includes the other
> > suggestions from Guenter.
> > 
> 
> It is not at all what I meant or asked for :-(. I'll have a closer
> look into the latest patch set later today.

An other attempt. This one has just the suggestions from you Guenter
(including the supports_usb_power_delivery attibute for cables as
well), and update to the ABI document.

Let me know if it's OK.


Thanks,

-- 
heikki

[-- Attachment #2: typec_v8.diff --]
[-- Type: text/plain, Size: 11460 bytes --]

diff --git a/Documentation/ABI/testing/sysfs-class-typec b/Documentation/ABI/testing/sysfs-class-typec
index 53fdd11..6b60dd4 100644
--- a/Documentation/ABI/testing/sysfs-class-typec
+++ b/Documentation/ABI/testing/sysfs-class-typec
@@ -6,10 +6,10 @@ 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 only supported as an asynchronous operation
-		and requires polling of the attribute in order to know the
-		result, so successful write operation does not mean successful
-		swap.
+		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.
 
 		Valid values:
 		- host
@@ -21,10 +21,10 @@ 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 only supported as an
-		asynchronous operation and requires polling of the attribute in
-		order to know the result, so successful write operation does not
-		mean successful swap.
+		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.
 
 		Valid values:
 		- source
@@ -37,6 +37,10 @@ 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.
 
 		Valid values are:
 		- 0 when the port is not the VCONN Source
@@ -101,6 +105,7 @@ Description:
 		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:		June 2016
@@ -113,28 +118,21 @@ Description:
 
 USB Type-C partner devices (eg. /sys/class/typec/usbc0-partner/)
 
-What:		/sys/class/typec/<port>-partner/accessory
+What:		/sys/class/typec/<port>-partner/accessory_mode
 Date:		June 2016
 Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
 Description:
-		The attribute is visible only when the partner's type is
-		"Accessory". The type can be read from its own attribute.
+		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.
 
-		Shows the name of the Accessory Mode. The Accessory Modes are
-		defined in USB Type-C Specification.
-
-What:		/sys/class/typec/<port>-partner/type
+What:		/sys/class/typec/<port>-partner/supports_usb_power_delivery
 Date:		June 2016
 Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
 Description:
-		Shows the type of the partner. Can be one of the following:
-		- USB - When the partner is normal USB host/peripheral.
-		- Charger - When the partner has been identified as dedicated
-			    charger.
-		- Alternate Mode - When the partner supports Alternate Modes.
-		- Accessory - When the partner is one of the accessories with
-			      specific Accessory Mode defined in USB Type-C
-			      specification.
+		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/)
@@ -165,6 +163,14 @@ Description:
 		- Type-C - USB Type-C
 		- Captive - Non-standard
 
+What:		/sys/class/typec/<port>-cable/supports_usb_power_delivery
+Date:		June 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
@@ -177,7 +183,15 @@ 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.
+		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.
+
+		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:		June 2016
diff --git a/drivers/usb/typec/typec.c b/drivers/usb/typec/typec.c
index 366a04c..ef2dbb8 100644
--- a/drivers/usb/typec/typec.c
+++ b/drivers/usb/typec/typec.c
@@ -42,10 +42,9 @@ static struct class typec_class = {
 };
 
 static const char * const typec_accessory_modes[] = {
-	[TYPEC_ACCESSORY_NONE]		= "none",
-	[TYPEC_ACCESSORY_AUDIO]		= "Audio",
-	[TYPEC_ACCESSORY_DEBUG]		= "Debug",
-	[TYPEC_ACCESSORY_DAUDIO]	= "Digital Audio",
+	[TYPEC_ACCESSORY_NONE]	= "no",
+	[TYPEC_ACCESSORY_AUDIO]	= "Audio Adapter Accessory Mode",
+	[TYPEC_ACCESSORY_DEBUG]	= "Debug Accessory Mode",
 };
 
 /* ------------------------------------------------------------------------- */
@@ -55,43 +54,34 @@ static void typec_dev_release(struct device *dev)
 {
 }
 
-static const char * const typec_partner_types[] = {
-	[TYPEC_PARTNER_USB]		= "USB",
-	[TYPEC_PARTNER_CHARGER]		= "Charger",
-	[TYPEC_PARTNER_ALTMODE]		= "Alternate Mode",
-	[TYPEC_PARTNER_ACCESSORY]	= "Accessory",
-};
-
-static ssize_t
-partner_type_show(struct device *dev, struct device_attribute *attr, char *buf)
+static ssize_t partner_usb_pd_show(struct device *dev,
+				   struct device_attribute *attr, char *buf)
 {
-	struct typec_partner *partner = container_of(dev, struct typec_partner,
-						     dev);
+	struct typec_partner *p = container_of(dev, struct typec_partner, dev);
 
-	return sprintf(buf, "%s\n", typec_partner_types[partner->type]);
+	return sprintf(buf, "%d\n", p->usb_pd);
 }
 
-static struct device_attribute dev_attr_partner_type = {
+static struct device_attribute dev_attr_partner_usb_pd = {
 	.attr = {
-		.name = "type",
+		.name = "supports_usb_power_delivery",
 		.mode = S_IRUGO,
 	},
-	.show = partner_type_show,
+	.show = partner_usb_pd_show,
 };
 
 static ssize_t
 partner_accessory_mode_show(struct device *dev, struct device_attribute *attr,
 			    char *buf)
 {
-	struct typec_partner *partner = container_of(dev, struct typec_partner,
-						     dev);
+	struct typec_partner *p = container_of(dev, struct typec_partner, dev);
 
-	return sprintf(buf, "%s\n", typec_accessory_modes[partner->accessory]);
+	return sprintf(buf, "%s\n", typec_accessory_modes[p->accessory]);
 }
 
 static struct device_attribute dev_attr_partner_accessory = {
 	.attr = {
-		.name = "accessory",
+		.name = "accessory_mode",
 		.mode = S_IRUGO,
 	},
 	.show = partner_accessory_mode_show,
@@ -99,27 +89,12 @@ static struct device_attribute dev_attr_partner_accessory = {
 
 static struct attribute *typec_partner_attrs[] = {
 	&dev_attr_partner_accessory.attr,
-	&dev_attr_partner_type.attr,
+	&dev_attr_partner_usb_pd.attr,
 	NULL
 };
 
-static umode_t
-partner_is_visible(struct kobject *kobj, struct attribute *attr, int n)
-{
-	struct device *dev = container_of(kobj, struct device, kobj);
-	struct typec_partner *partner = container_of(dev, struct typec_partner,
-						     dev);
-
-	if (attr == &dev_attr_partner_accessory.attr &&
-	    partner->type != TYPEC_PARTNER_ACCESSORY)
-		return 0;
-
-	return attr->mode;
-}
-
 static struct attribute_group typec_partner_group = {
 	.attrs = typec_partner_attrs,
-	.is_visible = partner_is_visible,
 };
 
 static const struct attribute_group *typec_partner_groups[] = {
@@ -215,6 +190,23 @@ static struct device_attribute dev_attr_cable_active = {
 	.show = cable_active_show,
 };
 
+static ssize_t
+cable_usb_pd_show(struct device *dev, struct device_attribute *attr,
+		       char *buf)
+{
+	struct typec_cable *cable = container_of(dev, struct typec_cable, dev);
+
+	return sprintf(buf, "%d\n", cable->usb_pd);
+}
+
+static struct device_attribute dev_attr_cable_usb_pd = {
+	.attr = {
+		.name = "supports_usb_power_delivery",
+		.mode = S_IRUGO,
+	},
+	.show = cable_usb_pd_show,
+};
+
 static const char * const typec_plug_types[] = {
 	[USB_PLUG_NONE]		= "unknown",
 	[USB_PLUG_TYPE_A]	= "Type-A",
@@ -242,6 +234,7 @@ static struct device_attribute dev_attr_plug_type = {
 
 static struct attribute *typec_cable_attrs[] = {
 	&dev_attr_cable_active.attr,
+	&dev_attr_cable_usb_pd.attr,
 	&dev_attr_plug_type.attr,
 	NULL
 };
@@ -925,15 +918,15 @@ static ssize_t supported_accessory_modes_show(struct device *dev,
 					      char *buf)
 {
 	struct typec_port *port = to_typec_port(dev);
-	enum typec_accessory *accessory;
 	ssize_t ret = 0;
 	int i;
 
-	if (port->cap->accessory)
-		for (accessory = port->cap->accessory, i = 0;
-		     i < port->cap->num_accessory; accessory++, i++)
-			ret += sprintf(buf, "%s\n",
-				       typec_accessory_modes[*accessory]);
+	if (!port->cap->accessory[0])
+		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);
diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
index eda6747..1270ca1 100644
--- a/include/linux/usb/typec.h
+++ b/include/linux/usb/typec.h
@@ -13,13 +13,6 @@ enum typec_port_type {
 	TYPEC_PORT_DRP,
 };
 
-enum typec_partner_type {
-	TYPEC_PARTNER_USB,
-	TYPEC_PARTNER_CHARGER,
-	TYPEC_PARTNER_ALTMODE,
-	TYPEC_PARTNER_ACCESSORY,
-};
-
 enum typec_plug_type {
 	USB_PLUG_NONE,
 	USB_PLUG_TYPE_A,
@@ -49,7 +42,6 @@ enum typec_accessory {
 	TYPEC_ACCESSORY_NONE,
 	TYPEC_ACCESSORY_AUDIO,
 	TYPEC_ACCESSORY_DEBUG,
-	TYPEC_ACCESSORY_DAUDIO,
 };
 
 /*
@@ -177,7 +169,6 @@ struct typec_cable {
  */
 struct typec_partner {
 	struct device		dev;
-	enum typec_partner_type	type;
 	unsigned int		usb_pd:1;
 	u32			vdo;
 	enum typec_accessory	accessory;
@@ -188,8 +179,7 @@ struct typec_partner {
  * 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
- * @accessory: Supported Accessory Modes
- * @num_accessory: Number of supported Accessory Modes
+ * @accessory: Supported Accessory Modes (NULL terminated array)
  * @alt_modes: Alternate Modes the connector supports (null terminated)
  * @try_role: Set a fixed data role for DRP port
  * @dr_set: Set Data Role
@@ -203,7 +193,6 @@ struct typec_capability {
 	enum typec_port_type	type;
 	unsigned int		usb_pd:1;
 	enum typec_accessory	*accessory;
-	unsigned int		num_accessory;
 	struct typec_altmode	*alt_modes;
 
 	int			(*try_role)(const struct typec_capability *,

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

* Re: [PATCHv6 1/3] usb: USB Type-C connector class
  2016-08-30 10:04     ` Heikki Krogerus
  2016-08-30 11:16       ` Oliver Neukum
  2016-08-30 13:46       ` Guenter Roeck
@ 2016-08-30 15:40       ` Guenter Roeck
  2 siblings, 0 replies; 28+ messages in thread
From: Guenter Roeck @ 2016-08-30 15:40 UTC (permalink / raw)
  To: Heikki Krogerus
  Cc: Oliver Neukum, Greg KH, Vincent Palatin, Bin Gao, Felipe Balbi,
	linux-kernel, linux-usb

Heikki,

On Tue, Aug 30, 2016 at 01:04:37PM +0300, Heikki Krogerus wrote:
> Hi Oliver,
> 
> On Tue, Aug 30, 2016 at 11:32:01AM +0200, Oliver Neukum wrote:
> > On Mon, 2016-08-29 at 15:36 +0300, Heikki Krogerus wrote:
> > > +What:          /sys/class/typec/<port>/current_data_role
> > > +Date:          June 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 only supported as an asynchronous
> > > operation
> > > +               and requires polling of the attribute in order to know
> > > the
> > > +               result, so successful write operation does not mean
> > > successful
> > > +               swap.
> > > +
> > 
> > That is badly formulated. Does it mean that poll() or select()
> > can be used or does the value need to be repearedly read?
> 
> Does polling not always mean poll/select?
> 
> > And how would you learn about an error?
> 
> This is what I'm also really worried about. I'm now wondering did I
> give up too easily on this to Guenter in hope to move this thing
> forward. He said it's problematic to do these calls synchronously for
> him. Was it something related to potential conflicting role swaps from
> both ends?
> 
> Guenter, can you please elaborate? And how do you plan to report
> failures with the swaps?
> 
Following up on this again. For the record, I never meant to suggest that the
ABI to userspace should be asynchronous. On the contrary, I think it should be
synchronous. Reason is that it simplifies user space for the general case, where
user space does not mind the wait and wants to get a valid return code as part
of the operation.

The more complex case, where user space _does_ mind the wait, can always be
handled by implementing a separate thread to write into sysfs attributes and
wait for the result. This can be hidden in an application library which
can also implement callbacks or signals to other threads as needed. It can
also be tied with udev event handling to observe actual role changes (which
will probably be necessary anyway).

While this may sound complicated, the code necessary to implement and support
an asynchronous kernel ABI would be at least as complicated, and would probably
also require a polling thread and callbacks to report results to other threads.
So I don't really see a gain for user space by providing an asynchronous
kernel ABI.

Thanks,
Guenter

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

* Re: [PATCHv6 1/3] usb: USB Type-C connector class
  2016-08-30 13:46       ` Guenter Roeck
@ 2016-08-30 14:13         ` Heikki Krogerus
  0 siblings, 0 replies; 28+ messages in thread
From: Heikki Krogerus @ 2016-08-30 14:13 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Oliver Neukum, Greg KH, Vincent Palatin, Bin Gao, Felipe Balbi,
	linux-kernel, linux-usb

On Tue, Aug 30, 2016 at 06:46:24AM -0700, Guenter Roeck wrote:
> On 08/30/2016 03:04 AM, Heikki Krogerus wrote:
> > Hi Oliver,
> > 
> > On Tue, Aug 30, 2016 at 11:32:01AM +0200, Oliver Neukum wrote:
> > > On Mon, 2016-08-29 at 15:36 +0300, Heikki Krogerus wrote:
> > > > +What:          /sys/class/typec/<port>/current_data_role
> > > > +Date:          June 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 only supported as an asynchronous
> > > > operation
> > > > +               and requires polling of the attribute in order to know
> > > > the
> > > > +               result, so successful write operation does not mean
> > > > successful
> > > > +               swap.
> > > > +
> > > 
> > > That is badly formulated. Does it mean that poll() or select()
> > > can be used or does the value need to be repearedly read?
> > 
> > Does polling not always mean poll/select?
> > 
> > > And how would you learn about an error?
> > 
> > This is what I'm also really worried about. I'm now wondering did I
> > give up too easily on this to Guenter in hope to move this thing
> > forward. He said it's problematic to do these calls synchronously for
> > him. Was it something related to potential conflicting role swaps from
> > both ends?
> > 
> > Guenter, can you please elaborate? And how do you plan to report
> > failures with the swaps?
> > 
> 
> I thought we had this sorted out. When I said "asynchronous", I did not mean
> that the sysfs operation would not wait for the operation to complete. I meant
> that the Type-C state machine operates in a different context than the sysfs/class
> code. Since the state machine operates in a different context, it may have
> to execute a callback into the class code at any time, independently of
> any pending role changes triggered through sysfs. Please have a look into
> the patch set I submitted for details. Roughly it works as follows.
> 
> Class code context				State machine context
> 
> User requests role change
> Class code calls {dr,pr,vconn}_set
> {dr,pr,vconn}_set code validates request
> {dr,pr,vconn}_set code sends role change
> 	request to state machine		State machine gets role change request
> {dr,pr,vconn}_set code waits for completion
> 						State machine sends role change request
> 						to link partner
> 						Partner reports Accept or Reject
> 						State machine changes state as requested
> 						State machine reports new role to class code
> 							via callbacks
> 						State machine informs Class code that request
> 						is complete
> {dr,pr,vconn}_set code gets results
> 	and returns to caller
> Class code reports results to user
> 
> From user perspective, everything is synchronous. However, the state machine has to be
> able to run independently and report role and other state changes to the class code while
> a role change request from the class code is pending. For example, it has to be able to
> handle incoming role change requests from the link partner, and it has to be able to
> handle link state changes. All those have to be reported to the class code. This is
> impossible if the class code holds a lock while a role change triggered from user space
> is pending, which is why I asked for the locks in the class code to be removed.
> 
> Maybe my use of the term "asynchronous" was misleading, and I should have said "operates
> in a different context" instead. My apologies.

Thanks for the explanation. I remember you did explain this before I
started my parental leave, but I forgot all about it.


Thanks,

-- 
heikki

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

* Re: [PATCHv6 1/3] usb: USB Type-C connector class
  2016-08-30 13:11           ` Heikki Krogerus
@ 2016-08-30 13:47             ` Guenter Roeck
  2016-08-31 11:09               ` Heikki Krogerus
  0 siblings, 1 reply; 28+ messages in thread
From: Guenter Roeck @ 2016-08-30 13:47 UTC (permalink / raw)
  To: Heikki Krogerus, Oliver Neukum
  Cc: Vincent Palatin, Bin Gao, Felipe Balbi, Greg KH, linux-kernel, linux-usb

On 08/30/2016 06:11 AM, Heikki Krogerus wrote:
> Hi,
>
> On Tue, Aug 30, 2016 at 02:49:50PM +0300, Heikki Krogerus wrote:
>> On Tue, Aug 30, 2016 at 01:16:46PM +0200, Oliver Neukum wrote:
>>> Error reporting does not require a synchronous operation. Reporting
>>> it in the next read() or write() and making it pollable is perfectly
>>> viable. It just must not be silently dropped.
>>
>> OK, I think I got it. I need to document that. I'll also add
>> get_pr/dr/vconn hooks to the API for getting the status.
>
> Would the attached diff do the trick? It also includes the other
> suggestions from Guenter.
>

It is not at all what I meant or asked for :-(. I'll have a closer
look into the latest patch set later today.

Guenter

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

* Re: [PATCHv6 1/3] usb: USB Type-C connector class
  2016-08-30 10:04     ` Heikki Krogerus
  2016-08-30 11:16       ` Oliver Neukum
@ 2016-08-30 13:46       ` Guenter Roeck
  2016-08-30 14:13         ` Heikki Krogerus
  2016-08-30 15:40       ` Guenter Roeck
  2 siblings, 1 reply; 28+ messages in thread
From: Guenter Roeck @ 2016-08-30 13:46 UTC (permalink / raw)
  To: Heikki Krogerus, Oliver Neukum
  Cc: Greg KH, Vincent Palatin, Bin Gao, Felipe Balbi, linux-kernel, linux-usb

On 08/30/2016 03:04 AM, Heikki Krogerus wrote:
> Hi Oliver,
>
> On Tue, Aug 30, 2016 at 11:32:01AM +0200, Oliver Neukum wrote:
>> On Mon, 2016-08-29 at 15:36 +0300, Heikki Krogerus wrote:
>>> +What:          /sys/class/typec/<port>/current_data_role
>>> +Date:          June 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 only supported as an asynchronous
>>> operation
>>> +               and requires polling of the attribute in order to know
>>> the
>>> +               result, so successful write operation does not mean
>>> successful
>>> +               swap.
>>> +
>>
>> That is badly formulated. Does it mean that poll() or select()
>> can be used or does the value need to be repearedly read?
>
> Does polling not always mean poll/select?
>
>> And how would you learn about an error?
>
> This is what I'm also really worried about. I'm now wondering did I
> give up too easily on this to Guenter in hope to move this thing
> forward. He said it's problematic to do these calls synchronously for
> him. Was it something related to potential conflicting role swaps from
> both ends?
>
> Guenter, can you please elaborate? And how do you plan to report
> failures with the swaps?
>

I thought we had this sorted out. When I said "asynchronous", I did not mean
that the sysfs operation would not wait for the operation to complete. I meant
that the Type-C state machine operates in a different context than the sysfs/class
code. Since the state machine operates in a different context, it may have
to execute a callback into the class code at any time, independently of
any pending role changes triggered through sysfs. Please have a look into
the patch set I submitted for details. Roughly it works as follows.

Class code context				State machine context

User requests role change
Class code calls {dr,pr,vconn}_set
{dr,pr,vconn}_set code validates request
{dr,pr,vconn}_set code sends role change
	request to state machine		State machine gets role change request
{dr,pr,vconn}_set code waits for completion
						State machine sends role change request
						to link partner
						Partner reports Accept or Reject
						State machine changes state as requested
						State machine reports new role to class code
							via callbacks
						State machine informs Class code that request
						is complete
{dr,pr,vconn}_set code gets results
	and returns to caller
Class code reports results to user

 From user perspective, everything is synchronous. However, the state machine has to be
able to run independently and report role and other state changes to the class code while
a role change request from the class code is pending. For example, it has to be able to
handle incoming role change requests from the link partner, and it has to be able to
handle link state changes. All those have to be reported to the class code. This is
impossible if the class code holds a lock while a role change triggered from user space
is pending, which is why I asked for the locks in the class code to be removed.

Maybe my use of the term "asynchronous" was misleading, and I should have said "operates
in a different context" instead. My apologies.

Thanks,
Guenter

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

* Re: [PATCHv6 1/3] usb: USB Type-C connector class
  2016-08-30 11:49         ` Heikki Krogerus
@ 2016-08-30 13:11           ` Heikki Krogerus
  2016-08-30 13:47             ` Guenter Roeck
  0 siblings, 1 reply; 28+ messages in thread
From: Heikki Krogerus @ 2016-08-30 13:11 UTC (permalink / raw)
  To: Oliver Neukum
  Cc: Guenter Roeck, Vincent Palatin, Bin Gao, Felipe Balbi, Greg KH,
	linux-kernel, linux-usb

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

Hi,

On Tue, Aug 30, 2016 at 02:49:50PM +0300, Heikki Krogerus wrote:
> On Tue, Aug 30, 2016 at 01:16:46PM +0200, Oliver Neukum wrote:
> > Error reporting does not require a synchronous operation. Reporting
> > it in the next read() or write() and making it pollable is perfectly
> > viable. It just must not be silently dropped.
> 
> OK, I think I got it. I need to document that. I'll also add
> get_pr/dr/vconn hooks to the API for getting the status.

Would the attached diff do the trick? It also includes the other
suggestions from Guenter.


Thanks,

-- 
heikki

[-- Attachment #2: typec_v8.diff --]
[-- Type: text/plain, Size: 14419 bytes --]

diff --git a/Documentation/ABI/testing/sysfs-class-typec b/Documentation/ABI/testing/sysfs-class-typec
index 53fdd11..9f249b2 100644
--- a/Documentation/ABI/testing/sysfs-class-typec
+++ b/Documentation/ABI/testing/sysfs-class-typec
@@ -6,10 +6,12 @@ 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 only supported as an asynchronous operation
-		and requires polling of the attribute in order to know the
-		result, so successful write operation does not mean successful
-		swap.
+		port. Swapping is only supported as an asynchronous operation,
+		so successful write operation does not mean successful swap.
+		The attribute can be polled with poll() or select() to get
+		notification on finished operation. In case of failures, the
+		following read/write after finished operation will return error
+		code identifying the cause of the failure.
 
 		Valid values:
 		- host
@@ -22,9 +24,11 @@ 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 only supported as an
-		asynchronous operation and requires polling of the attribute in
-		order to know the result, so successful write operation does not
-		mean successful swap.
+		asynchronous operation, so successful write operation does not
+		mean successful swap. The attribute can be polled with poll() or
+		select() to get notification on finished operation. In case of
+		failures, the following read/write after finished operation will
+		return error code identifying the cause of the failure.
 
 		Valid values:
 		- source
@@ -37,6 +41,12 @@ 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 only supported as an asynchronous operation, so
+		successful write operation does not mean successful swap. The
+		attribute can be polled with poll() or select() to get
+		notification on finished operation. In case of failures, the
+		following read/write after finished operation will return error
+		code identifying the cause of the failure.
 
 		Valid values are:
 		- 0 when the port is not the VCONN Source
@@ -113,28 +123,21 @@ Description:
 
 USB Type-C partner devices (eg. /sys/class/typec/usbc0-partner/)
 
-What:		/sys/class/typec/<port>-partner/accessory
+What:		/sys/class/typec/<port>-partner/accessory_mode
 Date:		June 2016
 Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
 Description:
-		The attribute is visible only when the partner's type is
-		"Accessory". The type can be read from its own attribute.
+		Shows the Accessory Mode name or "no" when the partner is not an
+		Accesory Mode. The Accessory Modes are defined in USB Type-C
+		Specification.
 
-		Shows the name of the Accessory Mode. The Accessory Modes are
-		defined in USB Type-C Specification.
-
-What:		/sys/class/typec/<port>-partner/type
+What:		/sys/class/typec/<port>-partner/supports_usb_power_delivery
 Date:		June 2016
 Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
 Description:
-		Shows the type of the partner. Can be one of the following:
-		- USB - When the partner is normal USB host/peripheral.
-		- Charger - When the partner has been identified as dedicated
-			    charger.
-		- Alternate Mode - When the partner supports Alternate Modes.
-		- Accessory - When the partner is one of the accessories with
-			      specific Accessory Mode defined in USB Type-C
-			      specification.
+		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/)
@@ -177,7 +180,13 @@ 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.
+		support for specific alternate modes. Entering/exiting modes is
+		only supported as an asynchronous operation, so successful write
+		operation does not mean successful swap. The attribute can be
+		polled with poll() or select() to get notification on finished
+		operation. In case of failures, the following read/write after
+		finished operation will return error code identifying the cause
+		of the failure.
 
 What:		/sys/class/typec/<dev>/<dev>.svid:<svid>/<mode>/description
 Date:		June 2016
diff --git a/drivers/usb/typec/typec.c b/drivers/usb/typec/typec.c
index 366a04c..6e549f4 100644
--- a/drivers/usb/typec/typec.c
+++ b/drivers/usb/typec/typec.c
@@ -42,10 +42,9 @@ static struct class typec_class = {
 };
 
 static const char * const typec_accessory_modes[] = {
-	[TYPEC_ACCESSORY_NONE]		= "none",
-	[TYPEC_ACCESSORY_AUDIO]		= "Audio",
-	[TYPEC_ACCESSORY_DEBUG]		= "Debug",
-	[TYPEC_ACCESSORY_DAUDIO]	= "Digital Audio",
+	[TYPEC_ACCESSORY_NONE]	= "no",
+	[TYPEC_ACCESSORY_AUDIO]	= "Audio Adapter Accessory Mode",
+	[TYPEC_ACCESSORY_DEBUG]	= "Debug Accessory Mode",
 };
 
 /* ------------------------------------------------------------------------- */
@@ -55,43 +54,34 @@ static void typec_dev_release(struct device *dev)
 {
 }
 
-static const char * const typec_partner_types[] = {
-	[TYPEC_PARTNER_USB]		= "USB",
-	[TYPEC_PARTNER_CHARGER]		= "Charger",
-	[TYPEC_PARTNER_ALTMODE]		= "Alternate Mode",
-	[TYPEC_PARTNER_ACCESSORY]	= "Accessory",
-};
-
-static ssize_t
-partner_type_show(struct device *dev, struct device_attribute *attr, char *buf)
+static ssize_t partner_usb_pd_show(struct device *dev,
+				   struct device_attribute *attr, char *buf)
 {
-	struct typec_partner *partner = container_of(dev, struct typec_partner,
-						     dev);
+	struct typec_partner *p = container_of(dev, struct typec_partner, dev);
 
-	return sprintf(buf, "%s\n", typec_partner_types[partner->type]);
+	return sprintf(buf, "%d\n", p->usb_pd);
 }
 
-static struct device_attribute dev_attr_partner_type = {
+static struct device_attribute dev_attr_partner_usb_pd = {
 	.attr = {
-		.name = "type",
+		.name = "supports_usb_power_delivery",
 		.mode = S_IRUGO,
 	},
-	.show = partner_type_show,
+	.show = partner_usb_pd_show,
 };
 
 static ssize_t
 partner_accessory_mode_show(struct device *dev, struct device_attribute *attr,
 			    char *buf)
 {
-	struct typec_partner *partner = container_of(dev, struct typec_partner,
-						     dev);
+	struct typec_partner *p = container_of(dev, struct typec_partner, dev);
 
-	return sprintf(buf, "%s\n", typec_accessory_modes[partner->accessory]);
+	return sprintf(buf, "%s\n", typec_accessory_modes[p->accessory]);
 }
 
 static struct device_attribute dev_attr_partner_accessory = {
 	.attr = {
-		.name = "accessory",
+		.name = "accessory_mode",
 		.mode = S_IRUGO,
 	},
 	.show = partner_accessory_mode_show,
@@ -99,27 +89,12 @@ static struct device_attribute dev_attr_partner_accessory = {
 
 static struct attribute *typec_partner_attrs[] = {
 	&dev_attr_partner_accessory.attr,
-	&dev_attr_partner_type.attr,
+	&dev_attr_partner_usb_pd.attr,
 	NULL
 };
 
-static umode_t
-partner_is_visible(struct kobject *kobj, struct attribute *attr, int n)
-{
-	struct device *dev = container_of(kobj, struct device, kobj);
-	struct typec_partner *partner = container_of(dev, struct typec_partner,
-						     dev);
-
-	if (attr == &dev_attr_partner_accessory.attr &&
-	    partner->type != TYPEC_PARTNER_ACCESSORY)
-		return 0;
-
-	return attr->mode;
-}
-
 static struct attribute_group typec_partner_group = {
 	.attrs = typec_partner_attrs,
-	.is_visible = partner_is_visible,
 };
 
 static const struct attribute_group *typec_partner_groups[] = {
@@ -495,6 +470,12 @@ typec_altmode_active_show(struct device *dev, struct device_attribute *attr,
 {
 	struct typec_mode *mode = container_of(attr, struct typec_mode,
 					       active_attr);
+	struct typec_port *port = typec_altmode2port(mode->alt_mode);
+	int ret;
+
+	ret = port->cap->mode_status(mode->alt_mode, mode->index);
+	if (ret)
+		return ret;
 
 	return sprintf(buf, "%d\n", mode->active);
 }
@@ -509,6 +490,10 @@ typec_altmode_active_store(struct device *dev, struct device_attribute *attr,
 	bool activate;
 	int ret;
 
+	ret = port->cap->mode_status(mode->alt_mode, mode->index);
+	if (ret)
+		return ret;
+
 	ret = kstrtobool(buf, &activate);
 	if (ret)
 		return ret;
@@ -768,6 +753,11 @@ current_data_role_store(struct device *dev, struct device_attribute *attr,
 		return -EOPNOTSUPP;
 	}
 
+	/* Get status from previous operation */
+	ret = port->cap->dr_get(port->cap);
+	if (ret < 0)
+		return ret;
+
 	ret = sysfs_strmatch(typec_data_roles, buf);
 	if (ret < 0)
 		return ret;
@@ -786,6 +776,11 @@ current_data_role_show(struct device *dev, struct device_attribute *attr,
 		       char *buf)
 {
 	struct typec_port *port = to_typec_port(dev);
+	int ret;
+
+	ret = port->cap->dr_get(port->cap);
+	if (ret < 0)
+		return ret;
 
 	return sprintf(buf, "%s\n", typec_data_roles[port->data_role]);
 }
@@ -827,6 +822,11 @@ static ssize_t current_power_role_store(struct device *dev,
 		return -EIO;
 	}
 
+	/* Get status from previous operation */
+	ret = port->cap->pr_get(port->cap);
+	if (ret < 0)
+		return ret;
+
 	ret = sysfs_strmatch(typec_roles, buf);
 	if (ret < 0)
 		return ret;
@@ -844,6 +844,11 @@ static ssize_t current_power_role_show(struct device *dev,
 				       struct device_attribute *attr, char *buf)
 {
 	struct typec_port *port = to_typec_port(dev);
+	int ret;
+
+	ret = port->cap->pr_get(port->cap);
+	if (ret < 0)
+		return ret;
 
 	return sprintf(buf, "%s\n", typec_roles[port->pwr_role]);
 }
@@ -897,13 +902,17 @@ static ssize_t vconn_source_store(struct device *dev,
 		return -EOPNOTSUPP;
 	}
 
-	if (sysfs_streq(buf, "1"))
-		role = TYPEC_SOURCE;
-	else if (sysfs_streq(buf, "0"))
+	if (sysfs_streq(buf, "0"))
 		role = TYPEC_SINK;
+	else if (sysfs_streq(buf, "1"))
+		role = TYPEC_SOURCE;
 	else
 		return -EINVAL;
 
+	ret = port->cap->vconn_get(port->cap);
+	if (ret < 0)
+		return ret;
+
 	ret = port->cap->vconn_set(port->cap, role);
 	if (ret)
 		return ret;
@@ -915,6 +924,11 @@ static ssize_t vconn_source_show(struct device *dev,
 				 struct device_attribute *attr, char *buf)
 {
 	struct typec_port *port = to_typec_port(dev);
+	int ret;
+
+	ret = port->cap->vconn_get(port->cap);
+	if (ret < 0)
+		return ret;
 
 	return sprintf(buf, "%d\n", port->vconn_role == TYPEC_SOURCE ? 1 : 0);
 }
@@ -925,15 +939,15 @@ static ssize_t supported_accessory_modes_show(struct device *dev,
 					      char *buf)
 {
 	struct typec_port *port = to_typec_port(dev);
-	enum typec_accessory *accessory;
 	ssize_t ret = 0;
 	int i;
 
-	if (port->cap->accessory)
-		for (accessory = port->cap->accessory, i = 0;
-		     i < port->cap->num_accessory; accessory++, i++)
-			ret += sprintf(buf, "%s\n",
-				       typec_accessory_modes[*accessory]);
+	if (!port->cap->accessory[0])
+		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);
@@ -1003,6 +1017,12 @@ struct typec_port *typec_register_port(struct device *dev,
 	int ret;
 	int id;
 
+	/* Forcing the drivers to report status */
+	BUG_ON(cap->dr_set && !cap->dr_get);
+	BUG_ON(cap->pr_set && !cap->pr_get);
+	BUG_ON(cap->vconn_set && !cap->vconn_get);
+	BUG_ON(cap->activate_mode && !cap->mode_status);
+
 	port = kzalloc(sizeof(*port), GFP_KERNEL);
 	if (!port)
 		return ERR_PTR(-ENOMEM);
diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
index eda6747..60a7376 100644
--- a/include/linux/usb/typec.h
+++ b/include/linux/usb/typec.h
@@ -13,13 +13,6 @@ enum typec_port_type {
 	TYPEC_PORT_DRP,
 };
 
-enum typec_partner_type {
-	TYPEC_PARTNER_USB,
-	TYPEC_PARTNER_CHARGER,
-	TYPEC_PARTNER_ALTMODE,
-	TYPEC_PARTNER_ACCESSORY,
-};
-
 enum typec_plug_type {
 	USB_PLUG_NONE,
 	USB_PLUG_TYPE_A,
@@ -49,7 +42,6 @@ enum typec_accessory {
 	TYPEC_ACCESSORY_NONE,
 	TYPEC_ACCESSORY_AUDIO,
 	TYPEC_ACCESSORY_DEBUG,
-	TYPEC_ACCESSORY_DAUDIO,
 };
 
 /*
@@ -177,7 +169,6 @@ struct typec_cable {
  */
 struct typec_partner {
 	struct device		dev;
-	enum typec_partner_type	type;
 	unsigned int		usb_pd:1;
 	u32			vdo;
 	enum typec_accessory	accessory;
@@ -188,14 +179,17 @@ struct typec_partner {
  * 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
- * @accessory: Supported Accessory Modes
- * @num_accessory: Number of supported Accessory Modes
+ * @accessory: Supported Accessory Modes (NULL terminated array)
  * @alt_modes: Alternate Modes the connector supports (null terminated)
  * @try_role: Set a fixed data role for DRP port
  * @dr_set: Set Data Role
+ * @dr_get: Get status from previous dr_set (mandatory with dr_set)
  * @pr_set: Set Power Role
+ * @pr_get: Get status from previous pr_get (mandatory with pr_set)
  * @vconn_set: Set VCONN Role
+ * @vconn_get: Get status from previous vconn_set (mandatory with vconn_set)
  * @activate_mode: Enter/exit given Alternate Mode
+ * @mode_status: Status from last activate_mode (mandatory with acivate_mode)
  *
  * Static capabilities of a single USB Type-C port.
  */
@@ -203,7 +197,6 @@ struct typec_capability {
 	enum typec_port_type	type;
 	unsigned int		usb_pd:1;
 	enum typec_accessory	*accessory;
-	unsigned int		num_accessory;
 	struct typec_altmode	*alt_modes;
 
 	int			(*try_role)(const struct typec_capability *,
@@ -211,13 +204,18 @@ struct typec_capability {
 
 	int			(*dr_set)(const struct typec_capability *,
 					  enum typec_data_role);
+	int			(*dr_get)(const struct typec_capability *);
 	int			(*pr_set)(const struct typec_capability *,
 					  enum typec_role);
+	int			(*pr_get)(const struct typec_capability *);
 	int			(*vconn_set)(const struct typec_capability *,
 					     enum typec_role);
+	int			(*vconn_get)(const struct typec_capability *);
 
 	int			(*activate_mode)(struct typec_altmode *,
 						 int mode, int activate);
+	int			(*mode_status)(struct typec_altmode *,
+					       int mode);
 };
 
 /*

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

* Re: [PATCHv6 1/3] usb: USB Type-C connector class
  2016-08-30 11:16       ` Oliver Neukum
@ 2016-08-30 11:49         ` Heikki Krogerus
  2016-08-30 13:11           ` Heikki Krogerus
  0 siblings, 1 reply; 28+ messages in thread
From: Heikki Krogerus @ 2016-08-30 11:49 UTC (permalink / raw)
  To: Oliver Neukum
  Cc: Guenter Roeck, Vincent Palatin, Bin Gao, Felipe Balbi, Greg KH,
	linux-kernel, linux-usb

On Tue, Aug 30, 2016 at 01:16:46PM +0200, Oliver Neukum wrote:
> On Tue, 2016-08-30 at 13:04 +0300, Heikki Krogerus wrote:
> > On Tue, Aug 30, 2016 at 11:32:01AM +0200, Oliver Neukum wrote:
> 
> Hi,
> 
> > > On Mon, 2016-08-29 at 15:36 +0300, Heikki Krogerus wrote:
> > > > +What:          /sys/class/typec/<port>/current_data_role
> > > > +Date:          June 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 only supported as an asynchronous
> > > > operation
> > > > +               and requires polling of the attribute in order to know
> > > > the
> > > > +               result, so successful write operation does not mean
> > > > successful
> > > > +               swap.
> > > > +
> > > 
> > > That is badly formulated. Does it mean that poll() or select()
> > > can be used or does the value need to be repearedly read?
> > 
> > Does polling not always mean poll/select?
> 
> No, it does not.
> 
> > > And how would you learn about an error?
> > 
> > This is what I'm also really worried about. I'm now wondering did I
> > give up too easily on this to Guenter in hope to move this thing
> > forward. He said it's problematic to do these calls synchronously for
> 
> Error reporting does not require a synchronous operation. Reporting
> it in the next read() or write() and making it pollable is perfectly
> viable. It just must not be silently dropped.

OK, I think I got it. I need to document that. I'll also add
get_pr/dr/vconn hooks to the API for getting the status.


Thanks Oliver,

-- 
heikki

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

* Re: [PATCHv6 1/3] usb: USB Type-C connector class
  2016-08-30 10:04     ` Heikki Krogerus
@ 2016-08-30 11:16       ` Oliver Neukum
  2016-08-30 11:49         ` Heikki Krogerus
  2016-08-30 13:46       ` Guenter Roeck
  2016-08-30 15:40       ` Guenter Roeck
  2 siblings, 1 reply; 28+ messages in thread
From: Oliver Neukum @ 2016-08-30 11:16 UTC (permalink / raw)
  To: Heikki Krogerus
  Cc: Guenter Roeck, Vincent Palatin, Bin Gao, Felipe Balbi, Greg KH,
	linux-kernel, linux-usb

On Tue, 2016-08-30 at 13:04 +0300, Heikki Krogerus wrote:
> On Tue, Aug 30, 2016 at 11:32:01AM +0200, Oliver Neukum wrote:

Hi,

> > On Mon, 2016-08-29 at 15:36 +0300, Heikki Krogerus wrote:
> > > +What:          /sys/class/typec/<port>/current_data_role
> > > +Date:          June 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 only supported as an asynchronous
> > > operation
> > > +               and requires polling of the attribute in order to know
> > > the
> > > +               result, so successful write operation does not mean
> > > successful
> > > +               swap.
> > > +
> > 
> > That is badly formulated. Does it mean that poll() or select()
> > can be used or does the value need to be repearedly read?
> 
> Does polling not always mean poll/select?

No, it does not.

> > And how would you learn about an error?
> 
> This is what I'm also really worried about. I'm now wondering did I
> give up too easily on this to Guenter in hope to move this thing
> forward. He said it's problematic to do these calls synchronously for

Error reporting does not require a synchronous operation. Reporting
it in the next read() or write() and making it pollable is perfectly
viable. It just must not be silently dropped.

	Regards
		Oliver

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

* Re: [PATCHv6 1/3] usb: USB Type-C connector class
  2016-08-30  9:32   ` Oliver Neukum
@ 2016-08-30 10:04     ` Heikki Krogerus
  2016-08-30 11:16       ` Oliver Neukum
                         ` (2 more replies)
  0 siblings, 3 replies; 28+ messages in thread
From: Heikki Krogerus @ 2016-08-30 10:04 UTC (permalink / raw)
  To: Oliver Neukum, Guenter Roeck
  Cc: Greg KH, Vincent Palatin, Bin Gao, Felipe Balbi, linux-kernel, linux-usb

Hi Oliver,

On Tue, Aug 30, 2016 at 11:32:01AM +0200, Oliver Neukum wrote:
> On Mon, 2016-08-29 at 15:36 +0300, Heikki Krogerus wrote:
> > +What:          /sys/class/typec/<port>/current_data_role
> > +Date:          June 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 only supported as an asynchronous
> > operation
> > +               and requires polling of the attribute in order to know
> > the
> > +               result, so successful write operation does not mean
> > successful
> > +               swap.
> > +
> 
> That is badly formulated. Does it mean that poll() or select()
> can be used or does the value need to be repearedly read?

Does polling not always mean poll/select?

> And how would you learn about an error?

This is what I'm also really worried about. I'm now wondering did I
give up too easily on this to Guenter in hope to move this thing
forward. He said it's problematic to do these calls synchronously for
him. Was it something related to potential conflicting role swaps from
both ends?

Guenter, can you please elaborate? And how do you plan to report
failures with the swaps?


Thanks,

-- 
heikki

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

* Re: [PATCHv6 1/3] usb: USB Type-C connector class
  2016-08-29 12:36 ` [PATCHv6 1/3] usb: USB Type-C connector class Heikki Krogerus
  2016-08-29 13:06   ` Vincent Palatin
@ 2016-08-30  9:32   ` Oliver Neukum
  2016-08-30 10:04     ` Heikki Krogerus
  1 sibling, 1 reply; 28+ messages in thread
From: Oliver Neukum @ 2016-08-30  9:32 UTC (permalink / raw)
  To: Heikki Krogerus
  Cc: Greg KH, Guenter Roeck, Vincent Palatin, Bin Gao, Felipe Balbi,
	linux-kernel, linux-usb

On Mon, 2016-08-29 at 15:36 +0300, Heikki Krogerus wrote:
> +What:          /sys/class/typec/<port>/current_data_role
> +Date:          June 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 only supported as an asynchronous
> operation
> +               and requires polling of the attribute in order to know
> the
> +               result, so successful write operation does not mean
> successful
> +               swap.
> +

That is badly formulated. Does it mean that poll() or select()
can be used or does the value need to be repearedly read?
And how would you learn about an error?

	Regards
		Oliver

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

* Re: [PATCHv6 1/3] usb: USB Type-C connector class
  2016-08-29 12:36 ` [PATCHv6 1/3] usb: USB Type-C connector class Heikki Krogerus
@ 2016-08-29 13:06   ` Vincent Palatin
  2016-08-30  9:32   ` Oliver Neukum
  1 sibling, 0 replies; 28+ messages in thread
From: Vincent Palatin @ 2016-08-29 13:06 UTC (permalink / raw)
  To: Heikki Krogerus
  Cc: Greg KH, Guenter Roeck, Oliver Neukum, Felipe Balbi, Bin Gao,
	LKML, linux-usb

On Mon, Aug 29, 2016 at 2:36 PM, Heikki Krogerus
<heikki.krogerus@linux.intel.com> 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>


Acked-by: Vincent Palatin <vpalatin@chromium.org>


> ---
>  Documentation/ABI/testing/sysfs-class-typec |  205 +++++
>  Documentation/usb/typec.txt                 |  103 +++
>  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                   | 1091 +++++++++++++++++++++++++++
>  include/linux/usb/typec.h                   |  260 +++++++
>  9 files changed, 1680 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..53fdd11
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-class-typec
> @@ -0,0 +1,205 @@
> +USB Type-C port devices (eg. /sys/class/typec/usbc0/)
> +
> +What:          /sys/class/typec/<port>/current_data_role
> +Date:          June 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 only supported as an asynchronous operation
> +               and requires polling of the attribute in order to know the
> +               result, so successful write operation does not mean successful
> +               swap.
> +
> +               Valid values:
> +               - host
> +               - device
> +
> +What:          /sys/class/typec/<port>/current_power_role
> +Date:          June 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 only supported as an
> +               asynchronous operation and requires polling of the attribute in
> +               order to know the result, so successful write operation does not
> +               mean successful swap.
> +
> +               Valid values:
> +               - source
> +               - sink
> +
> +What:          /sys/class/typec/<port>/vconn_source
> +Date:          June 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.
> +
> +               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:          June 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
> +               - BC1.2 - Power levels defined in Battery Charging Specification
> +                         v1.2
> +               - 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:          June 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:
> +               - host
> +               - device
> +               - For example "none" to remove preference (anything else except
> +                 "host" or "device")
> +
> +What:          /sys/class/typec/<port>/supported_accessory_modes
> +Date:          June 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:          June 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:          June 2016
> +Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> +               Lists the power roles the port is capable of supporting.
> +
> +               Valid values:
> +               - source
> +               - sink
> +
> +What:          /sys/class/typec/<port>/supports_usb_power_delivery
> +Date:          June 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
> +Date:          June 2016
> +Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> +               The attribute is visible only when the partner's type is
> +               "Accessory". The type can be read from its own attribute.
> +
> +               Shows the name of the Accessory Mode. The Accessory Modes are
> +               defined in USB Type-C Specification.
> +
> +What:          /sys/class/typec/<port>-partner/type
> +Date:          June 2016
> +Contact:       Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> +               Shows the type of the partner. Can be one of the following:
> +               - USB - When the partner is normal USB host/peripheral.
> +               - Charger - When the partner has been identified as dedicated
> +                           charger.
> +               - Alternate Mode - When the partner supports Alternate Modes.
> +               - Accessory - When the partner is one of the accessories with
> +                             specific Accessory Mode defined in USB Type-C
> +                             specification.
> +
> +
> +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:          June 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:          June 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
> +
> +
> +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:          June 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.
> +
> +What:          /sys/class/typec/<dev>/<dev>.svid:<svid>/<mode>/description
> +Date:          June 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:          June 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:          June 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..7095a2a
> --- /dev/null
> +++ b/Documentation/usb/typec.txt
> @@ -0,0 +1,103 @@
> +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);
> +
> +
> +* 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 d8e81b1..fe65f43 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -12370,6 +12370,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 8689dcb..f42a3d3 100644
> --- a/drivers/usb/Kconfig
> +++ b/drivers/usb/Kconfig
> @@ -150,6 +150,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 && USB_COMMON && LEDS_TRIGGERS
> diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
> index dca7856..51e381e 100644
> --- a/drivers/usb/Makefile
> +++ b/drivers/usb/Makefile
> @@ -61,3 +61,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..b229fb9
> --- /dev/null
> +++ b/drivers/usb/typec/Kconfig
> @@ -0,0 +1,7 @@
> +
> +menu "USB PD 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..ae0f691
> --- /dev/null
> +++ b/drivers/usb/typec/typec.c
> @@ -0,0 +1,1091 @@
> +/*
> + * 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)
> +
> +static DEFINE_IDA(typec_index_ida);
> +
> +static struct class typec_class = {
> +       .name = "typec",
> +};
> +
> +static const char * const typec_accessory_modes[] = {
> +       [TYPEC_ACCESSORY_NONE]          = "none",
> +       [TYPEC_ACCESSORY_AUDIO]         = "Audio",
> +       [TYPEC_ACCESSORY_DEBUG]         = "Debug",
> +       [TYPEC_ACCESSORY_DAUDIO]        = "Digital Audio",
> +};
> +
> +static int sysfs_strmatch(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;
> +}
> +
> +/* ------------------------------------------------------------------------- */
> +/* Type-C Partners */
> +
> +static void typec_dev_release(struct device *dev)
> +{
> +}
> +
> +static const char * const typec_partner_types[] = {
> +       [TYPEC_PARTNER_USB]             = "USB",
> +       [TYPEC_PARTNER_CHARGER]         = "Charger",
> +       [TYPEC_PARTNER_ALTMODE]         = "Alternate Mode",
> +       [TYPEC_PARTNER_ACCESSORY]       = "Accessory",
> +};
> +
> +static ssize_t
> +partner_type_show(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> +       struct typec_partner *partner = container_of(dev, struct typec_partner,
> +                                                    dev);
> +
> +       return sprintf(buf, "%s\n", typec_partner_types[partner->type]);
> +}
> +
> +static struct device_attribute dev_attr_partner_type = {
> +       .attr = {
> +               .name = "type",
> +               .mode = S_IRUGO,
> +       },
> +       .show = partner_type_show,
> +};
> +
> +static ssize_t
> +partner_accessory_mode_show(struct device *dev, struct device_attribute *attr,
> +                           char *buf)
> +{
> +       struct typec_partner *partner = container_of(dev, struct typec_partner,
> +                                                    dev);
> +
> +       return sprintf(buf, "%s\n", typec_accessory_modes[partner->accessory]);
> +}
> +
> +static struct device_attribute dev_attr_partner_accessory = {
> +       .attr = {
> +               .name = "accessory",
> +               .mode = S_IRUGO,
> +       },
> +       .show = partner_accessory_mode_show,
> +};
> +
> +static struct attribute *typec_partner_attrs[] = {
> +       &dev_attr_partner_accessory.attr,
> +       &dev_attr_partner_type.attr,
> +       NULL
> +};
> +
> +static umode_t
> +partner_is_visible(struct kobject *kobj, struct attribute *attr, int n)
> +{
> +       struct device *dev = container_of(kobj, struct device, kobj);
> +       struct typec_partner *partner = container_of(dev, struct typec_partner,
> +                                                    dev);
> +
> +       if (attr == &dev_attr_partner_accessory.attr &&
> +           partner->type != TYPEC_PARTNER_ACCESSORY)
> +               return 0;
> +
> +       return attr->mode;
> +}
> +
> +static struct attribute_group typec_partner_group = {
> +       .attrs = typec_partner_attrs,
> +       .is_visible = partner_is_visible,
> +};
> +
> +static const struct attribute_group *typec_partner_groups[] = {
> +       &typec_partner_group,
> +       NULL
> +};
> +
> +static struct device_type typec_partner_dev_type = {
> +       .name = "typec_partner_device",
> +       .groups = typec_partner_groups,
> +       .release = typec_dev_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)
> +{
> +       WARN_ON(port->partner->alt_modes);
> +       device_unregister(&port->partner->dev);
> +}
> +
> +/* ------------------------------------------------------------------------- */
> +/* Type-C Cable Plugs */
> +
> +static struct device_type typec_plug_dev_type = {
> +       .name = "typec_plug_device",
> +       .release = typec_dev_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)
> +{
> +       WARN_ON(plug->alt_modes);
> +       device_unregister(&plug->dev);
> +}
> +
> +/* Type-C Cables */
> +
> +static ssize_t
> +cable_active_show(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> +       struct typec_cable *cable = container_of(dev, struct typec_cable, dev);
> +
> +       return sprintf(buf, "%d\n", cable->active);
> +}
> +
> +static struct device_attribute dev_attr_cable_active = {
> +       .attr = {
> +               .name = "active",
> +               .mode = S_IRUGO,
> +       },
> +       .show = cable_active_show,
> +};
> +
> +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
> +cable_plug_type_show(struct device *dev, struct device_attribute *attr,
> +                    char *buf)
> +{
> +       struct typec_cable *cable = container_of(dev, struct typec_cable, dev);
> +
> +       return sprintf(buf, "%s\n", typec_plug_types[cable->type]);
> +}
> +
> +static struct device_attribute dev_attr_plug_type = {
> +       .attr = {
> +               .name = "plug_type",
> +               .mode = S_IRUGO,
> +       },
> +       .show = cable_plug_type_show,
> +};
> +
> +static struct attribute *typec_cable_attrs[] = {
> +       &dev_attr_cable_active.attr,
> +       &dev_attr_plug_type.attr,
> +       NULL
> +};
> +
> +static struct attribute_group typec_cable_group = {
> +       .attrs = typec_cable_attrs,
> +};
> +
> +static const struct attribute_group *typec_cable_groups[] = {
> +       &typec_cable_group,
> +       NULL
> +};
> +
> +static struct device_type typec_cable_dev_type = {
> +       .name = "typec_cable_device",
> +       .groups = typec_cable_groups,
> +       .release = typec_dev_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;
> +
> +       kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
> +
> +       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->partner = NULL;
> +       port->cable = NULL;
> +
> +       port->pwr_opmode = TYPEC_PWR_MODE_USB;
> +
> +       typec_init_roles(port);
> +
> +       kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
> +}
> +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");
> +}
> +EXPORT_SYMBOL(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");
> +}
> +EXPORT_SYMBOL(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");
> +}
> +EXPORT_SYMBOL(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");
> +}
> +EXPORT_SYMBOL(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");
> +}
> +EXPORT_SYMBOL(typec_altmode_update_active);
> +
> +static struct device_type typec_port_dev_type;
> +
> +/*
> + * 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;
> +
> +       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 = S_IRUGO;
> +               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 = S_IRUGO;
> +               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 = S_IWUSR | S_IRUGO;
> +               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 = S_IRUGO;
> +                       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 attibute_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 = container_of(dev,
> +                                                      struct typec_partner,
> +                                                      dev);
> +               p->alt_modes = alt_modes;
> +       } else if (dev->type == &typec_plug_dev_type) {
> +               struct typec_plug *p = container_of(dev, struct 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 = container_of(dev,
> +                                                      struct typec_partner,
> +                                                      dev);
> +               alt_modes = p->alt_modes;
> +               p->alt_modes = NULL;
> +       } else if (dev->type == &typec_plug_dev_type) {
> +               struct typec_plug *p = container_of(dev, struct 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);
> +       enum typec_role 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;
> +       }
> +
> +       ret = sysfs_strmatch(typec_roles, ARRAY_SIZE(typec_roles), buf);
> +       if (ret < 0) {
> +               port->prefer_role = -1;
> +               return size;
> +       }
> +
> +       role = ret;
> +
> +       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);
> +       enum typec_role role;
> +       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_strmatch(typec_data_roles, ARRAY_SIZE(typec_data_roles),
> +                            buf);
> +       if (ret < 0)
> +               return ret;
> +
> +       role = ret;
> +
> +       ret = port->cap->dr_set(port->cap, role);
> +       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);
> +       enum typec_role role;
> +       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_strmatch(typec_roles, ARRAY_SIZE(typec_roles), buf);
> +       if (ret < 0)
> +               return ret;
> +
> +       role = ret;
> +
> +       ret = port->cap->pr_set(port->cap, role);
> +       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);
> +       enum typec_accessory *accessory;
> +       ssize_t ret = 0;
> +       int i;
> +
> +       if (port->cap->accessory)
> +               for (accessory = port->cap->accessory, i = 0;
> +                    i < port->cap->num_accessory; accessory++, i++)
> +                       ret += sprintf(buf, "%s\n",
> +                                      typec_accessory_modes[*accessory]);
> +       return ret;
> +}
> +static DEVICE_ATTR_RO(supported_accessory_modes);
> +
> +static ssize_t supports_usb_power_delivery_show(struct device *dev,
> +                                               struct device_attribute *attr,
> +                                               char *buf)
> +{
> +       struct typec_port *port = to_typec_port(dev);
> +
> +       return sprintf(buf, "%d\n", port->cap->usb_pd);
> +}
> +static DEVICE_ATTR_RO(supports_usb_power_delivery);
> +
> +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,
> +};
> +
> +static const struct attribute_group typec_group = {
> +       .attrs = typec_attrs,
> +};
> +
> +static const struct attribute_group *typec_groups[] = {
> +       &typec_group,
> +       NULL,
> +};
> +
> +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);
> +       }
> +
> +       /* FIXME: a better approach for this */
> +       port->prefer_role = -1;
> +
> +       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)
> +{
> +       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..eda6747
> --- /dev/null
> +++ b/include/linux/usb/typec.h
> @@ -0,0 +1,260 @@
> +
> +#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_partner_type {
> +       TYPEC_PARTNER_USB,
> +       TYPEC_PARTNER_CHARGER,
> +       TYPEC_PARTNER_ALTMODE,
> +       TYPEC_PARTNER_ACCESSORY,
> +};
> +
> +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,
> +       TYPEC_ACCESSORY_DAUDIO,
> +};
> +
> +/*
> + * 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 *);
> +
> +void typec_altmode_update_active(struct typec_altmode *alt, int mode,
> +                                bool active);
> +
> +int typec_register_altmodes(struct device *, struct typec_altmode *);
> +void typec_unregister_altmodes(struct device *);
> +
> +/*
> + * 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;
> +       enum typec_partner_type type;
> +       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
> + * @accessory: Supported Accessory Modes
> + * @num_accessory: Number of supported Accessory Modes
> + * @alt_modes: Alternate Modes the connector supports (null terminated)
> + * @try_role: Set a fixed data role 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;
> +       enum typec_accessory    *accessory;
> +       unsigned int            num_accessory;
> +       struct typec_altmode    *alt_modes;
> +
> +       int                     (*try_role)(const struct typec_capability *,
> +                                           enum typec_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);
> +};
> +
> +/*
> + * 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 *, 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);
> +
> +#endif /* __LINUX_USB_TYPEC_H */
> --
> 2.8.1
>

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

* [PATCHv6 1/3] usb: USB Type-C connector class
  2016-08-29 12:36 [PATCHv6 0/3] USB Type-C Connector class Heikki Krogerus
@ 2016-08-29 12:36 ` Heikki Krogerus
  2016-08-29 13:06   ` Vincent Palatin
  2016-08-30  9:32   ` Oliver Neukum
  0 siblings, 2 replies; 28+ messages in thread
From: Heikki Krogerus @ 2016-08-29 12:36 UTC (permalink / raw)
  To: Greg KH, Guenter Roeck
  Cc: Oliver Neukum, Felipe Balbi, Bin Gao, Vincent Palatin,
	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 |  205 +++++
 Documentation/usb/typec.txt                 |  103 +++
 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                   | 1091 +++++++++++++++++++++++++++
 include/linux/usb/typec.h                   |  260 +++++++
 9 files changed, 1680 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..53fdd11
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-typec
@@ -0,0 +1,205 @@
+USB Type-C port devices (eg. /sys/class/typec/usbc0/)
+
+What:		/sys/class/typec/<port>/current_data_role
+Date:		June 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 only supported as an asynchronous operation
+		and requires polling of the attribute in order to know the
+		result, so successful write operation does not mean successful
+		swap.
+
+		Valid values:
+		- host
+		- device
+
+What:		/sys/class/typec/<port>/current_power_role
+Date:		June 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 only supported as an
+		asynchronous operation and requires polling of the attribute in
+		order to know the result, so successful write operation does not
+		mean successful swap.
+
+		Valid values:
+		- source
+		- sink
+
+What:		/sys/class/typec/<port>/vconn_source
+Date:		June 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.
+
+		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:		June 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
+		- BC1.2 - Power levels defined in Battery Charging Specification
+			  v1.2
+		- 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:		June 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:
+		- host
+		- device
+		- For example "none" to remove preference (anything else except
+		  "host" or "device")
+
+What:		/sys/class/typec/<port>/supported_accessory_modes
+Date:		June 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:		June 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:		June 2016
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Lists the power roles the port is capable of supporting.
+
+		Valid values:
+		- source
+		- sink
+
+What:		/sys/class/typec/<port>/supports_usb_power_delivery
+Date:		June 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
+Date:		June 2016
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		The attribute is visible only when the partner's type is
+		"Accessory". The type can be read from its own attribute.
+
+		Shows the name of the Accessory Mode. The Accessory Modes are
+		defined in USB Type-C Specification.
+
+What:		/sys/class/typec/<port>-partner/type
+Date:		June 2016
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Shows the type of the partner. Can be one of the following:
+		- USB - When the partner is normal USB host/peripheral.
+		- Charger - When the partner has been identified as dedicated
+			    charger.
+		- Alternate Mode - When the partner supports Alternate Modes.
+		- Accessory - When the partner is one of the accessories with
+			      specific Accessory Mode defined in USB Type-C
+			      specification.
+
+
+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:		June 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:		June 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
+
+
+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:		June 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.
+
+What:		/sys/class/typec/<dev>/<dev>.svid:<svid>/<mode>/description
+Date:		June 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:		June 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:		June 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..7095a2a
--- /dev/null
+++ b/Documentation/usb/typec.txt
@@ -0,0 +1,103 @@
+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);
+
+
+* 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 d8e81b1..fe65f43 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12370,6 +12370,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 8689dcb..f42a3d3 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -150,6 +150,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 && USB_COMMON && LEDS_TRIGGERS
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index dca7856..51e381e 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile
@@ -61,3 +61,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..b229fb9
--- /dev/null
+++ b/drivers/usb/typec/Kconfig
@@ -0,0 +1,7 @@
+
+menu "USB PD 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..ae0f691
--- /dev/null
+++ b/drivers/usb/typec/typec.c
@@ -0,0 +1,1091 @@
+/*
+ * 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)
+
+static DEFINE_IDA(typec_index_ida);
+
+static struct class typec_class = {
+	.name = "typec",
+};
+
+static const char * const typec_accessory_modes[] = {
+	[TYPEC_ACCESSORY_NONE]		= "none",
+	[TYPEC_ACCESSORY_AUDIO]		= "Audio",
+	[TYPEC_ACCESSORY_DEBUG]		= "Debug",
+	[TYPEC_ACCESSORY_DAUDIO]	= "Digital Audio",
+};
+
+static int sysfs_strmatch(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;
+}
+
+/* ------------------------------------------------------------------------- */
+/* Type-C Partners */
+
+static void typec_dev_release(struct device *dev)
+{
+}
+
+static const char * const typec_partner_types[] = {
+	[TYPEC_PARTNER_USB]		= "USB",
+	[TYPEC_PARTNER_CHARGER]		= "Charger",
+	[TYPEC_PARTNER_ALTMODE]		= "Alternate Mode",
+	[TYPEC_PARTNER_ACCESSORY]	= "Accessory",
+};
+
+static ssize_t
+partner_type_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct typec_partner *partner = container_of(dev, struct typec_partner,
+						     dev);
+
+	return sprintf(buf, "%s\n", typec_partner_types[partner->type]);
+}
+
+static struct device_attribute dev_attr_partner_type = {
+	.attr = {
+		.name = "type",
+		.mode = S_IRUGO,
+	},
+	.show = partner_type_show,
+};
+
+static ssize_t
+partner_accessory_mode_show(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct typec_partner *partner = container_of(dev, struct typec_partner,
+						     dev);
+
+	return sprintf(buf, "%s\n", typec_accessory_modes[partner->accessory]);
+}
+
+static struct device_attribute dev_attr_partner_accessory = {
+	.attr = {
+		.name = "accessory",
+		.mode = S_IRUGO,
+	},
+	.show = partner_accessory_mode_show,
+};
+
+static struct attribute *typec_partner_attrs[] = {
+	&dev_attr_partner_accessory.attr,
+	&dev_attr_partner_type.attr,
+	NULL
+};
+
+static umode_t
+partner_is_visible(struct kobject *kobj, struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct typec_partner *partner = container_of(dev, struct typec_partner,
+						     dev);
+
+	if (attr == &dev_attr_partner_accessory.attr &&
+	    partner->type != TYPEC_PARTNER_ACCESSORY)
+		return 0;
+
+	return attr->mode;
+}
+
+static struct attribute_group typec_partner_group = {
+	.attrs = typec_partner_attrs,
+	.is_visible = partner_is_visible,
+};
+
+static const struct attribute_group *typec_partner_groups[] = {
+	&typec_partner_group,
+	NULL
+};
+
+static struct device_type typec_partner_dev_type = {
+	.name = "typec_partner_device",
+	.groups = typec_partner_groups,
+	.release = typec_dev_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)
+{
+	WARN_ON(port->partner->alt_modes);
+	device_unregister(&port->partner->dev);
+}
+
+/* ------------------------------------------------------------------------- */
+/* Type-C Cable Plugs */
+
+static struct device_type typec_plug_dev_type = {
+	.name = "typec_plug_device",
+	.release = typec_dev_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)
+{
+	WARN_ON(plug->alt_modes);
+	device_unregister(&plug->dev);
+}
+
+/* Type-C Cables */
+
+static ssize_t
+cable_active_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct typec_cable *cable = container_of(dev, struct typec_cable, dev);
+
+	return sprintf(buf, "%d\n", cable->active);
+}
+
+static struct device_attribute dev_attr_cable_active = {
+	.attr = {
+		.name = "active",
+		.mode = S_IRUGO,
+	},
+	.show = cable_active_show,
+};
+
+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
+cable_plug_type_show(struct device *dev, struct device_attribute *attr,
+		     char *buf)
+{
+	struct typec_cable *cable = container_of(dev, struct typec_cable, dev);
+
+	return sprintf(buf, "%s\n", typec_plug_types[cable->type]);
+}
+
+static struct device_attribute dev_attr_plug_type = {
+	.attr = {
+		.name = "plug_type",
+		.mode = S_IRUGO,
+	},
+	.show = cable_plug_type_show,
+};
+
+static struct attribute *typec_cable_attrs[] = {
+	&dev_attr_cable_active.attr,
+	&dev_attr_plug_type.attr,
+	NULL
+};
+
+static struct attribute_group typec_cable_group = {
+	.attrs = typec_cable_attrs,
+};
+
+static const struct attribute_group *typec_cable_groups[] = {
+	&typec_cable_group,
+	NULL
+};
+
+static struct device_type typec_cable_dev_type = {
+	.name = "typec_cable_device",
+	.groups = typec_cable_groups,
+	.release = typec_dev_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;
+
+	kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
+
+	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->partner = NULL;
+	port->cable = NULL;
+
+	port->pwr_opmode = TYPEC_PWR_MODE_USB;
+
+	typec_init_roles(port);
+
+	kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
+}
+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");
+}
+EXPORT_SYMBOL(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");
+}
+EXPORT_SYMBOL(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");
+}
+EXPORT_SYMBOL(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");
+}
+EXPORT_SYMBOL(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");
+}
+EXPORT_SYMBOL(typec_altmode_update_active);
+
+static struct device_type typec_port_dev_type;
+
+/*
+ * 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;
+
+	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 = S_IRUGO;
+		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 = S_IRUGO;
+		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 = S_IWUSR | S_IRUGO;
+		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 = S_IRUGO;
+			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 attibute_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 = container_of(dev,
+						       struct typec_partner,
+						       dev);
+		p->alt_modes = alt_modes;
+	} else if (dev->type == &typec_plug_dev_type) {
+		struct typec_plug *p = container_of(dev, struct 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 = container_of(dev,
+						       struct typec_partner,
+						       dev);
+		alt_modes = p->alt_modes;
+		p->alt_modes = NULL;
+	} else if (dev->type == &typec_plug_dev_type) {
+		struct typec_plug *p = container_of(dev, struct 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);
+	enum typec_role 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;
+	}
+
+	ret = sysfs_strmatch(typec_roles, ARRAY_SIZE(typec_roles), buf);
+	if (ret < 0) {
+		port->prefer_role = -1;
+		return size;
+	}
+
+	role = ret;
+
+	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);
+	enum typec_role role;
+	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_strmatch(typec_data_roles, ARRAY_SIZE(typec_data_roles),
+			     buf);
+	if (ret < 0)
+		return ret;
+
+	role = ret;
+
+	ret = port->cap->dr_set(port->cap, role);
+	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);
+	enum typec_role role;
+	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_strmatch(typec_roles, ARRAY_SIZE(typec_roles), buf);
+	if (ret < 0)
+		return ret;
+
+	role = ret;
+
+	ret = port->cap->pr_set(port->cap, role);
+	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);
+	enum typec_accessory *accessory;
+	ssize_t ret = 0;
+	int i;
+
+	if (port->cap->accessory)
+		for (accessory = port->cap->accessory, i = 0;
+		     i < port->cap->num_accessory; accessory++, i++)
+			ret += sprintf(buf, "%s\n",
+				       typec_accessory_modes[*accessory]);
+	return ret;
+}
+static DEVICE_ATTR_RO(supported_accessory_modes);
+
+static ssize_t supports_usb_power_delivery_show(struct device *dev,
+						struct device_attribute *attr,
+						char *buf)
+{
+	struct typec_port *port = to_typec_port(dev);
+
+	return sprintf(buf, "%d\n", port->cap->usb_pd);
+}
+static DEVICE_ATTR_RO(supports_usb_power_delivery);
+
+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,
+};
+
+static const struct attribute_group typec_group = {
+	.attrs = typec_attrs,
+};
+
+static const struct attribute_group *typec_groups[] = {
+	&typec_group,
+	NULL,
+};
+
+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);
+	}
+
+	/* FIXME: a better approach for this */
+	port->prefer_role = -1;
+
+	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)
+{
+	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..eda6747
--- /dev/null
+++ b/include/linux/usb/typec.h
@@ -0,0 +1,260 @@
+
+#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_partner_type {
+	TYPEC_PARTNER_USB,
+	TYPEC_PARTNER_CHARGER,
+	TYPEC_PARTNER_ALTMODE,
+	TYPEC_PARTNER_ACCESSORY,
+};
+
+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,
+	TYPEC_ACCESSORY_DAUDIO,
+};
+
+/*
+ * 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 *);
+
+void typec_altmode_update_active(struct typec_altmode *alt, int mode,
+				 bool active);
+
+int typec_register_altmodes(struct device *, struct typec_altmode *);
+void typec_unregister_altmodes(struct device *);
+
+/*
+ * 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;
+	enum typec_partner_type	type;
+	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
+ * @accessory: Supported Accessory Modes
+ * @num_accessory: Number of supported Accessory Modes
+ * @alt_modes: Alternate Modes the connector supports (null terminated)
+ * @try_role: Set a fixed data role 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;
+	enum typec_accessory	*accessory;
+	unsigned int		num_accessory;
+	struct typec_altmode	*alt_modes;
+
+	int			(*try_role)(const struct typec_capability *,
+					    enum typec_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);
+};
+
+/*
+ * 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 *, 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);
+
+#endif /* __LINUX_USB_TYPEC_H */
-- 
2.8.1

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

end of thread, other threads:[~2016-08-31 13:18 UTC | newest]

Thread overview: 28+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-08-22 12:05 [PATCHv6 0/3] USB Type-C Connector class Heikki Krogerus
2016-08-22 12:05 ` [PATCHv6 1/3] usb: USB Type-C connector class Heikki Krogerus
2016-08-24 14:08   ` Vincent Palatin
2016-08-25 11:59     ` Heikki Krogerus
2016-08-26 13:16       ` Vincent Palatin
2016-08-26 14:07         ` Heikki Krogerus
2016-08-29 13:04           ` Guenter Roeck
2016-08-29 13:43             ` Heikki Krogerus
2016-08-29 14:07               ` Heikki Krogerus
2016-08-29 18:50                 ` Guenter Roeck
2016-08-30  8:22                   ` Heikki Krogerus
2016-08-30 15:28                     ` Guenter Roeck
2016-08-30 17:00                     ` Guenter Roeck
2016-08-22 12:05 ` [PATCHv6 2/3] usb: typec: add driver for Intel Whiskey Cove PMIC USB Type-C PHY Heikki Krogerus
2016-08-22 12:06 ` [PATCHv6 3/3] mfd: intel_soc_pmic_bxtwc: add support for USB Type-C PHY on WhiskeyCove Heikki Krogerus
2016-08-29 12:36 [PATCHv6 0/3] USB Type-C Connector class Heikki Krogerus
2016-08-29 12:36 ` [PATCHv6 1/3] usb: USB Type-C connector class Heikki Krogerus
2016-08-29 13:06   ` Vincent Palatin
2016-08-30  9:32   ` Oliver Neukum
2016-08-30 10:04     ` Heikki Krogerus
2016-08-30 11:16       ` Oliver Neukum
2016-08-30 11:49         ` Heikki Krogerus
2016-08-30 13:11           ` Heikki Krogerus
2016-08-30 13:47             ` Guenter Roeck
2016-08-31 11:09               ` Heikki Krogerus
2016-08-31 13:18                 ` Guenter Roeck
2016-08-30 13:46       ` Guenter Roeck
2016-08-30 14:13         ` Heikki Krogerus
2016-08-30 15:40       ` Guenter Roeck

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).