All of lore.kernel.org
 help / color / mirror / Atom feed
From: Heikki Krogerus <heikki.krogerus@linux.intel.com>
To: Greg KH <gregkh@linuxfoundation.org>, Guenter Roeck <linux@roeck-us.net>
Cc: Oliver Neukum <oneukum@suse.com>,
	Felipe Balbi <felipe.balbi@linux.intel.com>,
	linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org
Subject: [PATCH v5 1/2] usb: USB Type-C connector class
Date: Wed, 17 Aug 2016 13:34:40 +0300	[thread overview]
Message-ID: <1471430081-12860-2-git-send-email-heikki.krogerus@linux.intel.com> (raw)
In-Reply-To: <1471430081-12860-1-git-send-email-heikki.krogerus@linux.intel.com>

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                   | 1104 +++++++++++++++++++++++++++
 include/linux/usb/typec.h                   |  260 +++++++
 9 files changed, 1687 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 45c9848..9e64ac2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12305,6 +12305,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..52a0431
--- /dev/null
+++ b/drivers/usb/typec/typec.c
@@ -0,0 +1,1104 @@
+/*
+ * 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;
+
+	mode->active = activate;
+	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;
+	}
+
+	if (!port->connected)
+		return size;
+
+	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;
+
+	port->data_role = role;
+	sysfs_notify(&port->dev.kobj, NULL, "current_data_role");
+
+	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;
+	}
+
+	if (!port->connected)
+		return size;
+
+	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);
+		return ret;
+
+	port->pwr_role = role;
+	sysfs_notify(&port->dev.kobj, NULL, "current_power_role");
+
+	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);
+		return ret;
+
+	port->pwr_role = role;
+	sysfs_notify(&port->dev.kobj, NULL, "current_vconn_role");
+
+	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

  reply	other threads:[~2016-08-17 10:35 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-08-17 10:34 [PATCH v5 0/2] USB Type-C Connector class Heikki Krogerus
2016-08-17 10:34 ` Heikki Krogerus [this message]
2016-08-17 13:14   ` [PATCH v5 1/2] usb: USB Type-C connector class Frans Klaver
2016-08-17 13:53     ` Heikki Krogerus
2016-08-17 14:00       ` Frans Klaver
2016-08-17 13:30   ` Guenter Roeck
2016-08-17 13:56     ` Heikki Krogerus
2016-08-17 17:53   ` Guenter Roeck
2016-08-18 10:43     ` Heikki Krogerus
2016-08-17 17:58   ` Guenter Roeck
2016-08-18 10:44     ` Heikki Krogerus
2016-08-17 10:34 ` [PATCH v5 2/2] usb: typec: add driver for Intel Whiskey Cove PMIC USB Type-C PHY Heikki Krogerus
2016-08-17 12:53   ` Felipe Balbi
2016-08-17 13:32     ` Heikki Krogerus
2016-08-17 23:00     ` Peter Chen
2016-08-18  6:33       ` Felipe Balbi
2016-08-18 13:32         ` Guenter Roeck

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1471430081-12860-2-git-send-email-heikki.krogerus@linux.intel.com \
    --to=heikki.krogerus@linux.intel.com \
    --cc=felipe.balbi@linux.intel.com \
    --cc=gregkh@linuxfoundation.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-usb@vger.kernel.org \
    --cc=linux@roeck-us.net \
    --cc=oneukum@suse.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.