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@xxxxxxxxxxxxxxx>
---
Documentation/ABI/testing/sysfs-class-typec | 220 ++++++
Documentation/usb/typec.txt | 174 +++++
MAINTAINERS | 9 +
drivers/usb/Kconfig | 2 +
drivers/usb/Makefile | 2 +
drivers/usb/typec/Kconfig | 7 +
drivers/usb/typec/Makefile | 1 +
drivers/usb/typec/typec.c | 1047 +++++++++++++++++++++++++++
include/linux/usb/typec.h | 212 ++++++
9 files changed, 1674 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-class-typec
create mode 100644 Documentation/usb/typec.txt
create mode 100644 drivers/usb/typec/Kconfig
create mode 100644 drivers/usb/typec/Makefile
create mode 100644 drivers/usb/typec/typec.c
create mode 100644 include/linux/usb/typec.h
Hi,
There was one more change to the ABI: the ports are now named
"port<id>" instead of "usbc<id>". Let me know if that is OK!
The series is still being reviewed internally, but I'm sending this
already here so you guys can take a look.
The API now expects the drivers to register every part separately,
including the alternate modes for the port itself. Structures for the
partner/cable/plug/altmode are now protected, and the drivers need to
register those with separate descriptor structures and get a handle to
struct typec_partner/cable/plug/altmode if the registration is
successful, or just NULL in case of failure as Greg proposed.
As the drivers need to register the partners and the cables separately
in any case, the drivers do not declare connections with the class
anymore. If the default role changes during initial negotiation with a
partner, the drivers need to notify the class before registering the
partner or cable.
Thanks,
diff --git a/Documentation/ABI/testing/sysfs-class-typec b/Documentation/ABI/testing/sysfs-class-typec
new file mode 100644
index 0000000..663709a
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-typec
@@ -0,0 +1,220 @@
+USB Type-C port devices (eg. /sys/class/typec/port0/)
+
+What: /sys/class/typec/<port>/current_data_role
+Date: February 2017
+Contact: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
+Description:
+ The current USB data role the port is operating in. This
+ attribute can be used for requesting data role swapping on the
+ port. Swapping is supported as synchronous operation, so
+ write(2) to the attribute will not return until the operation
+ has finished. The attribute is notified about role changes so
+ that poll(2) on the attribute wakes up. Change on the role will
+ also generate uevent KOBJ_CHANGE on the port.
+
+ Valid values:
+ - host
+ - device
+
+What: /sys/class/typec/<port>/current_power_role
+Date: February 2017
+Contact: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
+Description:
+ The current power role of the port. This attribute can be used
+ to request power role swap on the port when the port supports
+ USB Power Delivery. Swapping is supported as synchronous
+ operation, so write(2) to the attribute will not return until
+ the operation has finished. The attribute is notified about role
+ changes so that poll(2) on the attribute wakes up. Change on the
+ role will also generate uevent KOBJ_CHANGE.
+
+ Valid values:
+ - source
+ - sink
+
+What: /sys/class/typec/<port>/vconn_source
+Date: February 2017
+Contact: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
+Description:
+ Shows is the port VCONN Source. This attribute can be used to
+ request VCONN swap to change the VCONN Source during connection
+ when both the port and the partner support USB Power Delivery.
+ Swapping is supported as synchronous operation, so write(2) to
+ the attribute will not return until the operation has finished.
+ The attribute is notified about VCONN source changes so that
+ poll(2) on the attribute wakes up. Change on VCONN source also
+ generates uevent KOBJ_CHANGE.
+
+ Valid values are:
+ - 0 when the port is not the VCONN Source
+ - 1 when the port is the VCONN Source
+
+What: /sys/class/typec/<port>/power_operation_mode
+Date: February 2017
+Contact: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
+Description:
+ Shows the current power operational mode the port is in.
+
+ Valid values:
+ - USB - Normal power levels defined in USB specifications
+ - USB Type-C 1.5A - Higher 1.5A current defined in USB Type-C
+ specification.
+ - USB Type-C 3.0A - Higher 3A current defined in USB Type-C
+ specification.
+ - USB Power Delivery - The voltages and currents defined in USB
+ Power Delivery specification
+
+What: /sys/class/typec/<port>/preferred_role
+Date: February 2017
+Contact: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
+Description:
+ The user space can notify the driver about the preferred role.
+ It should be handled as enabling of Try.SRC or Try.SNK, as
+ defined in USB Type-C specification, in the port drivers. By
+ default there is no preferred role.
+
+ Valid values:
+ - source
+ - sink
+ - none - to remove preference
+
+What: /sys/class/typec/<port>/supported_accessory_modes
+Date: February 2017
+Contact: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
+Description:
+ Lists the Accessory Modes, defined in the USB Type-C
+ specification, the port supports.
+
+What: /sys/class/typec/<port>/supported_data_roles
+Date: February 2017
+Contact: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
+Description:
+ Lists the USB data roles the port is capable of supporting.
+
+ Valid values:
+ - device
+ - host
+ - device, host (DRD as defined in USB Type-C specification v1.2)
+
+What: /sys/class/typec/<port>/supported_power_roles
+Date: February 2017
+Contact: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
+Description:
+ Lists the power roles the port is capable of supporting.
+
+ Valid values:
+ - source
+ - sink
+ - source, sink (DRP as defined in USB Type-C specification v1.2)
+
+What: /sys/class/typec/<port>/supports_usb_power_delivery
+Date: February 2017
+Contact: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
+Description:
+ Shows if the port supports USB Power Delivery.
+ - 1 if USB Power Delivery is supported
+ - 0 when it's not
+
+
+USB Type-C partner devices (eg. /sys/class/typec/port0-partner/)
+
+What: /sys/class/typec/<port>-partner/accessory_mode
+Date: February 2017
+Contact: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
+Description:
+ Shows the Accessory Mode name, or "no" when the partner does not
+ support Accesory Modes. The Accessory Modes are defined in USB
+ Type-C Specification.
+
+What: /sys/class/typec/<port>-partner/supports_usb_power_delivery
+Date: February 2017
+Contact: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
+Description:
+ Shows if the partner supports USB Power Delivery:
+ - 0 when USB Power Delivery is not supported
+ - 1 when USB Power Delivery is supported
+
+
+USB Type-C cable devices (eg. /sys/class/typec/port0-cable/)
+
+Note: Electronically Marked Cables will have a device also for one cable plug
+(eg. /sys/class/typec/port0-plug0). If the cable is active and has also SOP
+Double Prime controller (USB Power Deliver specification ch. 2.4) it will have
+second device also for the other plug. Both plugs may have their alternate modes
+as described in USB Type-C and USB Power Delivery specifications.
+
+What: /sys/class/typec/<port>-cable/active
+Date: February 2017
+Contact: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
+Description:
+ Shows if the cable is active or passive.
+
+ Valid values:
+ - 0 when the cable is passive
+ - 1 when the cable is active
+
+What: /sys/class/typec/<port>-cable/plug_type
+Date: February 2017
+Contact: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
+Description:
+ Shows type of the plug on the cable:
+ - Type-A - Standard A
+ - Type-B - Standard B
+ - Type-C - USB Type-C
+ - Captive - Non-standard
+
+What: /sys/class/typec/<port>-cable/supports_usb_power_delivery
+Date: February 2017
+Contact: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
+Description:
+ Shows if the cable supports USB Power Delivery:
+ - 0 when USB Power Delivery is not supported
+ - 1 when USB Power Delivery is supported
+
+
+Alternate Mode devices (For example,
+/sys/class/typec/port0-partner/port0-partner.svid:xxxx/). The ports, partners
+and cable plugs can have alternate modes.
+
+What: /sys/class/typec/<dev>/<dev>.svid:<svid>/<mode>/active
+Date: February 2017
+Contact: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
+Description:
+ Shows if the mode is active or not. The attribute can be used
+ for entering/exiting the mode with partners and cable plugs, and
+ with the port alternate modes it can be used for disabling
+ support for specific alternate modes. Entering/exiting modes is
+ supported as synchronous operation so write(2) to the attribute
+ does not return until the enter/exit mode operation has
+ finished. The attribute is notified when the mode is
+ entered/exited so poll(2) on the attribute wakes up.
+ Entering/exiting a mode will also generate uevent KOBJ_CHANGE.
+
+ Valid values:
+ - 0 when the mode is deactive
+ - 1 when the mode is active
+
+What: /sys/class/typec/<dev>/<dev>.svid:<svid>/<mode>/description
+Date: February 2017
+Contact: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
+Description:
+ Shows description of the mode. The description is optional for
+ the drivers, just like with the Billboard Devices.
+
+What: /sys/class/typec/<dev>/<dev>.svid:<svid>/<mode>/vdo
+Date: February 2017
+Contact: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
+Description:
+ Shows the VDO in hexadecimal returned from the Discover Modes
+ command.
+
+What: /sys/class/typec/<port>/<port>.svid:<svid>/<mode>/supported_roles
+Date: February 2017
+Contact: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
+Description:
+ Shows the roles, source or sink, the mode is supported with.
+
+ This attribute is available for the devices describing the
+ alternate modes a port supports, and it will not be exposed with
+ the devices presenting the alternate modes the partners or cable
+ plugs support.
diff --git a/Documentation/usb/typec.txt b/Documentation/usb/typec.txt
new file mode 100644
index 0000000..e588657
--- /dev/null
+++ b/Documentation/usb/typec.txt
@@ -0,0 +1,174 @@
+USB Type-C connector class
+==========================
+
+Introduction
+------------
+The typec class is meant for describing the USB Type-C ports in a system to the
+user space in unified fashion. The class is designed to provide nothing else
+except the user space interface implementation in hope that it can be utilized
+on as many platforms as possible.
+
+The platforms are expected to register every USB Type-C port they have with the
+class. In a normal case the registration will be done by a USB Type-C or PD PHY
+driver, but it may be a driver for firmware interface such as UCSI, driver for
+USB PD controller or even driver for Thunderbolt3 controller. This document
+considers the component registering the USB Type-C ports with the class as "port
+driver".
+
+On top of showing the capabilities, the class also offer user space control over
+the roles and alternate modes of ports, partners and cable plugs when the port
+driver is capable of supporting those features.
+
+The class provides an API for the port drivers described in this document. The
+attributes are described in Documentation/ABI/testing/sysfs-class-typec.
+
+
+Interface
+---------
+Every port will be presented as its own device under /sys/class/typec/. The
+first port will be named "port0", the second "port1" and so on.
+
+When connected, the partner will be presented also as its own device under
+/sys/class/typec/. The parent of the partner device will always be the port. The
+partner attached to port "port0" will be named "port0-partner". Full path to the
+device would be /sys/class/typec/usb0/usb0-partner/.
+
+The cable and the two plugs on it may also be optionally presented as their own
+devices under /sys/class/typec/. The cable attached to the port "port0" port
+will be named port0-cable and the plug on the SOP Prime end (see USB Power
+Delivery Specification ch. 2.4) will be named "port0-plug0" and on the SOP
+Double Prime end "port0-plug1". The parent of a cable will always be the port,
+and the parent of the cable plugs will always be the cable.
+
+If the port, partner or cable plug support Alternate Modes, every Alternate Mode
+SVID will have their own device describing them. The Alternate Modes will not be
+attached to the typec class. For the port's "port0" partner, the Alternate Modes
+would have devices presented under /sys/class/typec/port0-partner/. Every mode
+that is supported will have its own group under the Alternate Mode device named
+"mode<index>". For example /sys/class/typec/port0/port0.svid:xxxx/mode0/. The
+requests for entering/exiting a mode happens with "active" attribute file in
+that group.
+
+
+API
+---
+
+The functions and structures of the class are defined in
+linux/include/usb/typec.h header.
+
+
+* Registering the ports
+
+The port drivers will describe every Type-C port they control with struct
+typec_capability data structure, and register them with the following API:
+
+struct typec_port *typec_register_port(struct device *parent,
+ const struct typec_capability *cap);
+
+The class will provide handle to struct typec_port on success and NULL on
+failure. The un-registration of the port happens with the following API:
+
+void typec_unregister_port(struct typec_port *port);
+
+When registering the ports, the prefer_role member in struct typec_capability
+deservers special notice. If the port that is being registered does not have
+initial role preference, which means the port does not execute Try.SNK or
+Try.SRC by default, the member must have value TYPEC_NO_PREFERRED_ROLE.
+Otherwise if the port executes Try.SNK by default the member must have value
+TYPEC_DEVICE and with Try.SRC the value must be TYPEC_HOST.
+
+
+* Registering Partners
+
+After successful connection of a partner, the port driver needs to register the
+partner with the class. Details about the partner need to be described in struct
+typec_partner_desc. The class copies the details of the partner during
+registration. The class offers the following API for registering/unregistering
+partners.
+
+struct typec_partner *typec_register_partner(struct typec_port *port,
+ struct typec_partner_desc *desc);
+void typec_unregister_partner(struct typec_partner *partner);
+
+The class will provide a handle to struct typec_partner if the registration was
+successful, or NULL.
+
+
+* Registering Cables
+
+After successful connection of a cable that supports USB Power Delivery
+Structured VDM Discover Identity, the port driver needs to register the cable
+and one or maximum two plugs the cable has with the class. Details about the
+cable need to be described in struct typec_cable_desc and about the plug in
+struct typec_plug_desc. The class copies the details during registration. The
+class offers the following API for registering/unregistering cables and their
+plugs:
+
+struct typec_cable *typec_register_cable(struct typec_port *port,
+ struct typec_cable_desc *desc);
+void typec_unregister_cable(struct typec_cable *cable);
+
+struct typec_plug *typec_register_plug(struct typec_cable *cable,
+ struct typec_plug_desc *desc);
+void typec_unregister_plug(struct typec_plug *plug);
+
+The class will provide a handle to struct typec_cable and struct typec_plug if
+the registration is successful, or NULL if it isn't.
+
+
+* Notifications
+
+When the partner end has executed a role change, or when the default roles change
+during connection of a partner or cable, the port driver must use the following
+APIs to report it to the class:
+
+void typec_set_data_role(struct typec_port *, enum typec_data_role);
+void typec_set_pwr_role(struct typec_port *, enum typec_role);
+void typec_set_vconn_role(struct typec_port *, enum typec_role);
+void typec_set_pwr_opmode(struct typec_port *, enum typec_pwr_opmode);
+
+
+* Alternate Modes
+
+USB Type-C Ports, Partners and Cable Plugs may support Alternate Modes with USB
+Type-C. Each Alternate Mode will have SVID, which is either a Standard ID given
+by USB-IF or vendor ID. Each SVID will support 1 - 7 modes. The class provides
+struct typec_mode_desc for describing individual mode of a SVID, and struct
+typec_altmode_desc which is a container of all the modes of a SVID that a
+port, partner or cable plug supports.
+
+Ports that support Alternate Modes need to register each SVID they support with
+the following API:
+
+struct typec_altmode
+*typec_port_register_altmode(struct typec_port *port,
+ struct typec_altmode_desc *desc);
+
+If a partner or cable plug provides a list of SVIDs as response to USB Power
+Delivery Structured VDM Discover SVIDs message, each SVID needs to be registered
+with the following API.
+
+API for the partners:
+
+struct typec_altmode
+*typec_partner_register_altmode(struct typec_partner *partner,
+ struct typec_altmode_desc *desc);
+
+API for the Cable Plugs:
+
+struct typec_altmode
+*typec_plug_register_altmode(struct typec_plug *plug,
+ struct typec_altmode_desc *desc);
+
+So ports, partners and cable plugs will register the alternate modes with their
+own functions, but the registration will always return a handle to struct
+typec_altmode on success, or NULL. The unregistration will happen with the same
+function:
+
+void typec_unregister_altmode(struct typec_altmode *altmode);
+
+If a partner or cable plug enters or exits a mode, the port driver needs to
+notify the class with the following API:
+
+void typec_altmode_update_active(struct typec_altmode *alt, int mode,
+ bool active);
diff --git a/MAINTAINERS b/MAINTAINERS
index 10581bd..bcc88cd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12873,6 +12873,15 @@ F: drivers/usb/
F: include/linux/usb.h
F: include/linux/usb/
+USB TYPEC SUBSYSTEM
+M: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
+L: linux-usb@xxxxxxxxxxxxxxx
+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@xxxxxxxxxxxxxxxxxxx>
L: linux-usb@xxxxxxxxxxxxxxx
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index fbe493d..89c322e 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -152,6 +152,8 @@ source "drivers/usb/phy/Kconfig"
source "drivers/usb/gadget/Kconfig"
+source "drivers/usb/typec/Kconfig"
+
config USB_LED_TRIG
bool "USB LED Triggers"
depends on LEDS_CLASS && LEDS_TRIGGERS
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index 7791af6..c7f4098 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile
@@ -62,3 +62,5 @@ obj-$(CONFIG_USB_GADGET) += gadget/
obj-$(CONFIG_USB_COMMON) += common/
obj-$(CONFIG_USBIP_CORE) += usbip/
+
+obj-$(CONFIG_TYPEC) += typec/
diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
new file mode 100644
index 0000000..17792f9
--- /dev/null
+++ b/drivers/usb/typec/Kconfig
@@ -0,0 +1,7 @@
+
+menu "USB Power Delivery and Type-C drivers"
+
+config TYPEC
+ tristate
+
+endmenu
diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
new file mode 100644
index 0000000..1012a8b
--- /dev/null
+++ b/drivers/usb/typec/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_TYPEC) += typec.o
diff --git a/drivers/usb/typec/typec.c b/drivers/usb/typec/typec.c
new file mode 100644
index 0000000..0e9dd2e
--- /dev/null
+++ b/drivers/usb/typec/typec.c
@@ -0,0 +1,1047 @@
+/*
+ * USB Type-C Connector Class
+ *
+ * Copyright (C) 2017, Intel Corporation
+ * Author: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb/typec.h>
+
+/* XXX: Once we have a header for USB Power Delivery, this belongs there */
+#define ALTMODE_MAX_N_MODES 7
+
+struct typec_mode {
+ int index;
+ u32 vdo;
+ char *desc;
+ enum typec_port_type roles;
+
+ struct typec_altmode *alt_mode;
+
+ unsigned int active:1;
+
+ char group_name[6];
+ struct attribute_group group;
+ struct attribute *attrs[5];
+ struct device_attribute vdo_attr;
+ struct device_attribute desc_attr;
+ struct device_attribute active_attr;
+ struct device_attribute roles_attr;
+};
+
+struct typec_altmode {
+ struct device dev;
+ u16 svid;
+ int n_modes;
+ struct typec_mode modes[ALTMODE_MAX_N_MODES];
+ const struct attribute_group *mode_groups[ALTMODE_MAX_N_MODES];
+};
+
+struct typec_plug {
+ struct device dev;
+ enum typec_plug_index index;
+};
+
+struct typec_cable {
+ struct device dev;
+ u16 pd_revision;
+ enum typec_plug_type type;
+ u32 vdo;
+ unsigned int active:1;
+};
+
+struct typec_partner {
+ struct device dev;
+ u16 pd_revision;
+ u32 vdo;
+ enum typec_accessory accessory;
+};
+
+struct typec_port {
+ unsigned int id;
+ struct device dev;
+
+ int prefer_role;
+ enum typec_data_role data_role;
+ enum typec_role pwr_role;
+ enum typec_role vconn_role;
+ enum typec_pwr_opmode pwr_opmode;
+
+ const struct typec_capability *cap;
+};
+
+#define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev)
+#define to_typec_plug(_dev_) container_of(_dev_, struct typec_plug, dev)
+#define to_typec_cable(_dev_) container_of(_dev_, struct typec_cable, dev)
+#define to_typec_partner(_dev_) container_of(_dev_, struct typec_partner, dev)
+#define to_altmode(_dev_) container_of(_dev_, struct typec_altmode, dev)
+
+static struct device_type typec_partner_dev_type;
+static struct device_type typec_cable_dev_type;
+static struct device_type typec_plug_dev_type;
+static struct device_type typec_port_dev_type;
+
+#define is_typec_partner(_dev_) (_dev_->type == &typec_partner_dev_type)
+#define is_typec_cable(_dev_) (_dev_->type == &typec_cable_dev_type)
+#define is_typec_plug(_dev_) (_dev_->type == &typec_plug_dev_type)
+#define is_typec_port(_dev_) (_dev_->type == &typec_port_dev_type)
+
+static DEFINE_IDA(typec_index_ida);
+static struct class *typec_class;
+
+/* Common attributes */
+
+static const char * const typec_accessory_modes[] = {
+ [TYPEC_ACCESSORY_NONE] = "",
+ [TYPEC_ACCESSORY_AUDIO] = "Audio Adapter Accessory Mode",
+ [TYPEC_ACCESSORY_DEBUG] = "Debug Accessory Mode",
+};
+
+static ssize_t usb_power_delivery_revision_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ u16 rev = 0;
+
+ if (is_typec_partner(dev)) {
+ struct typec_partner *p = to_typec_partner(dev);
+
+ rev = p->pd_revision;
+ } else if (is_typec_cable(dev)) {
+ struct typec_cable *p = to_typec_cable(dev);
+
+ rev = p->pd_revision;
+ } else if (is_typec_port(dev)) {
+ struct typec_port *p = to_typec_port(dev);
+
+ rev = p->cap->pd_revision;
+ }
+
+ return sprintf(buf, "%d\n", (rev >> 8) & 0xff);
+}
+static DEVICE_ATTR_RO(usb_power_delivery_revision);
+
+static ssize_t
+vdo_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ u32 vdo = 0;
+
+ if (is_typec_partner(dev)) {
+ struct typec_partner *p = to_typec_partner(dev);
+
+ vdo = p->vdo;
+ } else if (is_typec_cable(dev)) {
+ struct typec_cable *p = to_typec_cable(dev);
+
+ vdo = p->vdo;
+ }
+
+ return sprintf(buf, "0x%08x\n", vdo);
+}
+static DEVICE_ATTR_RO(vdo);
+
+/* ------------------------------------------------------------------------- */
+/* Alternate Modes */
+
+/*
+ * typec_altmode_update_active - Notify about Enter/Exit mode
+ * @alt: Handle to the Alternate Mode
+ * @mode: Mode index
+ * @active: True when the mode has been enterred
+ */
+void typec_altmode_update_active(struct typec_altmode *alt, int mode,
+ bool active)
+{
+ struct typec_mode *m = &alt->modes[mode];
+ char dir[6];
+
+ if (m->active == active)
+ return;
+
+ m->active = active;
+ snprintf(dir, 6, "mode%d", mode);
+ sysfs_notify(&alt->dev.kobj, dir, "active");
+ kobject_uevent(&alt->dev.kobj, KOBJ_CHANGE);
+}
+EXPORT_SYMBOL_GPL(typec_altmode_update_active);
+
+/*
+ * typec_altmode2port - Alternate Mode to USB Type-C port
+ * @alt: The Alternate Mode
+ *
+ * Returns the port that the cable plug or partner with @alt is connected to.
+ */
+struct typec_port *typec_altmode2port(struct typec_altmode *alt)
+{
+ if (is_typec_plug(alt->dev.parent))
+ return to_typec_port(alt->dev.parent->parent->parent);
+ if (is_typec_partner(alt->dev.parent))
+ return to_typec_port(alt->dev.parent->parent);
+ if (is_typec_port(alt->dev.parent))
+ return to_typec_port(alt->dev.parent);
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(typec_altmode2port);
+
+static void typec_altmode_release(struct device *dev)
+{
+ struct typec_altmode *alt = to_altmode(dev);
+ int i;
+
+ for (i = 0; i < alt->n_modes; i++)
+ kfree(alt->modes[i].desc);
+ kfree(alt);
+}
+
+static ssize_t
+typec_altmode_vdo_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct typec_mode *mode = container_of(attr, struct typec_mode,
+ vdo_attr);
+
+ return sprintf(buf, "0x%08x\n", mode->vdo);
+}
+
+static ssize_t
+typec_altmode_desc_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct typec_mode *mode = container_of(attr, struct typec_mode,
+ desc_attr);
+
+ return sprintf(buf, "%s\n", mode->desc ? mode->desc : "");
+}
+
+static ssize_t
+typec_altmode_active_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct typec_mode *mode = container_of(attr, struct typec_mode,
+ active_attr);
+
+ return sprintf(buf, "%d\n", mode->active);
+}
+
+static ssize_t
+typec_altmode_active_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct typec_mode *mode = container_of(attr, struct typec_mode,
+ active_attr);
+ struct typec_port *port = typec_altmode2port(mode->alt_mode);
+ bool activate;
+ int ret;
+
+ if (!port->cap->activate_mode)
+ return -EOPNOTSUPP;
+
+ ret = kstrtobool(buf, &activate);
+ if (ret)
+ return ret;
+
+ ret = port->cap->activate_mode(port->cap, mode->index, activate);
+ if (ret)
+ return ret;
+
+ return size;
+}
+
+static ssize_t
+typec_altmode_roles_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct typec_mode *mode = container_of(attr, struct typec_mode,
+ roles_attr);
+ ssize_t ret;
+
+ switch (mode->roles) {
+ case TYPEC_PORT_DFP:
+ ret = sprintf(buf, "source\n");
+ break;
+ case TYPEC_PORT_UFP:
+ ret = sprintf(buf, "sink\n");
+ break;
+ case TYPEC_PORT_DRP:
+ default:
+ ret = sprintf(buf, "source\nsink\n");
+ break;
+ }
+ return ret;
+}
+
+static inline void typec_init_modes(struct typec_altmode *alt,
+ struct typec_mode_desc *desc, bool is_port)
+{
+ int i;
+
+ for (i = 0; i < alt->n_modes; i++, desc++) {
+ struct typec_mode *mode = &alt->modes[i];
+
+ /* Not considering the human readable description critical */
+ mode->desc = kstrdup(desc->desc, GFP_KERNEL);
+ if (desc->desc && !mode->desc)
+ dev_err(&alt->dev, "failed to copy mode%d desc\n", i);
+
+ mode->alt_mode = alt;
+ mode->vdo = desc->vdo;
+ mode->roles = desc->roles;
+ mode->index = desc->index;
+ sprintf(mode->group_name, "mode%d", desc->index);
+
+ sysfs_attr_init(&mode->vdo_attr.attr);
+ mode->vdo_attr.attr.name = "vdo";
+ mode->vdo_attr.attr.mode = 0444;
+ mode->vdo_attr.show = typec_altmode_vdo_show;
+
+ sysfs_attr_init(&mode->desc_attr.attr);
+ mode->desc_attr.attr.name = "description";
+ mode->desc_attr.attr.mode = 0444;
+ mode->desc_attr.show = typec_altmode_desc_show;
+
+ sysfs_attr_init(&mode->active_attr.attr);
+ mode->active_attr.attr.name = "active";
+ mode->active_attr.attr.mode = 0644;
+ mode->active_attr.show = typec_altmode_active_show;
+ mode->active_attr.store = typec_altmode_active_store;
+
+ mode->attrs[0] = &mode->vdo_attr.attr;
+ mode->attrs[1] = &mode->desc_attr.attr;
+ mode->attrs[2] = &mode->active_attr.attr;
+
+ /* With ports, list the roles that the mode is supported with */
+ if (is_port) {
+ sysfs_attr_init(&mode->roles_attr.attr);
+ mode->roles_attr.attr.name = "supported_roles";
+ mode->roles_attr.attr.mode = 0444;
+ mode->roles_attr.show = typec_altmode_roles_show;
+
+ mode->attrs[3] = &mode->roles_attr.attr;
+ }
+
+ mode->group.attrs = mode->attrs;
+ mode->group.name = mode->group_name;
+
+ alt->mode_groups[i] = &mode->group;
+ }
+}
+
+static struct typec_altmode
+*typec_register_altmode(struct device *parent, struct typec_altmode_desc *desc)
+{
+ struct typec_altmode *alt;
+ int ret;
+
+ alt = kzalloc(sizeof(*alt), GFP_KERNEL);
+ if (!alt)
+ return NULL;
+
+ alt->svid = desc->svid;
+ alt->n_modes = desc->n_modes;
+ typec_init_modes(alt, desc->modes, is_typec_port(parent));
+
+ alt->dev.parent = parent;
+ alt->dev.groups = alt->mode_groups;
+ alt->dev.release = typec_altmode_release;
+ dev_set_name(&alt->dev, "%s.svid:%04x", dev_name(parent), alt->svid);
+
+ ret = device_register(&alt->dev);
+ if (ret) {
+ int i;
+
+ dev_err(parent, "failed to register alternate mode (%d)\n",
+ ret);
+
+ put_device(&alt->dev);
+
+ for (i = 0; i < alt->n_modes; i++)
+ kfree(alt->modes[i].desc);
+ kfree(alt);
+ return NULL;
+ }
+
+ return alt;
+}
+
+void typec_unregister_altmode(struct typec_altmode *alt)
+{
+ if (alt)
+ device_unregister(&alt->dev);
+}
+EXPORT_SYMBOL_GPL(typec_unregister_altmode);
+
+/* ------------------------------------------------------------------------- */
+/* Type-C Partners */
+
+static ssize_t accessory_mode_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct typec_partner *p = to_typec_partner(dev);
+
+ return sprintf(buf, "%s\n", typec_accessory_modes[p->accessory]);
+}
+static DEVICE_ATTR_RO(accessory_mode);
+
+static struct attribute *typec_partner_attrs[] = {
+ &dev_attr_vdo.attr,
+ &dev_attr_accessory_mode.attr,
+ &dev_attr_usb_power_delivery_revision.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(typec_partner);
+
+static void typec_partner_release(struct device *dev)
+{
+ struct typec_partner *partner = to_typec_partner(dev);
+
+ kfree(partner);
+}
+
+static struct device_type typec_partner_dev_type = {
+ .name = "typec_partner_device",
+ .groups = typec_partner_groups,
+ .release = typec_partner_release,
+};
+
+struct typec_altmode
+*typec_partner_register_altmode(struct typec_partner *partner,
+ struct typec_altmode_desc *desc)
+{
+ return typec_register_altmode(&partner->dev, desc);
+}
+EXPORT_SYMBOL_GPL(typec_partner_register_altmode);
+
+struct typec_partner *typec_register_partner(struct typec_port *port,
+ struct typec_partner_desc *desc)
+{
+ struct typec_partner *partner = NULL;
+ int ret;
+
+ partner = kzalloc(sizeof(*partner), GFP_KERNEL);
+ if (!partner)
+ return NULL;
+
+ partner->vdo = desc->vdo;
+ partner->accessory = desc->accessory;
+ partner->pd_revision = desc->pd_revision;
+
+ partner->dev.class = typec_class;
+ partner->dev.parent = &port->dev;
+ partner->dev.type = &typec_partner_dev_type;
+ dev_set_name(&partner->dev, "%s-partner", dev_name(&port->dev));
+
+ ret = device_register(&partner->dev);
+ if (ret) {
+ dev_err(&port->dev, "failed to register partner (%d)\n", ret);
+ put_device(&partner->dev);
+ kfree(partner);
+ return NULL;
+ }
+
+ return partner;
+}
+EXPORT_SYMBOL_GPL(typec_register_partner);
+
+void typec_unregister_partner(struct typec_partner *partner)
+{
+ if (partner)
+ device_unregister(&partner->dev);
+}
+EXPORT_SYMBOL_GPL(typec_unregister_partner);
+
+/* ------------------------------------------------------------------------- */
+/* Type-C Cable Plugs */
+
+static void typec_plug_release(struct device *dev)
+{
+ struct typec_plug *plug = to_typec_plug(dev);
+
+ kfree(plug);
+}
+
+static struct device_type typec_plug_dev_type = {
+ .name = "typec_plug_device",
+ .release = typec_plug_release,
+};
+
+struct typec_altmode
+*typec_plug_register_altmode(struct typec_plug *plug,
+ struct typec_altmode_desc *desc)
+{
+ return typec_register_altmode(&plug->dev, desc);
+}
+EXPORT_SYMBOL_GPL(typec_plug_register_altmode);
+
+struct typec_plug *typec_register_plug(struct typec_cable *cable,
+ struct typec_plug_desc *desc)
+{
+ struct typec_plug *plug = NULL;
+ char name[8];
+ int ret;
+
+ plug = kzalloc(sizeof(*plug), GFP_KERNEL);
+ if (!plug)
+ return NULL;
+
+ sprintf(name, "plug%d", desc->index);
+
+ plug->index = desc->index;
+ plug->dev.class = typec_class;
+ plug->dev.parent = &cable->dev;
+ plug->dev.type = &typec_plug_dev_type;
+ dev_set_name(&plug->dev, "%s-%s", dev_name(cable->dev.parent), name);
+
+ ret = device_register(&plug->dev);
+ if (ret) {
+ dev_err(&cable->dev, "failed to register plug (%d)\n", ret);
+ put_device(&plug->dev);
+ kfree(plug);
+ return NULL;
+ }
+
+ return plug;
+}
+EXPORT_SYMBOL_GPL(typec_register_plug);
+
+void typec_unregister_plug(struct typec_plug *plug)
+{
+ if (plug)
+ device_unregister(&plug->dev);
+}
+EXPORT_SYMBOL_GPL(typec_unregister_plug);
+
+/* Type-C Cables */
+
+static ssize_t
+active_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct typec_cable *cable = to_typec_cable(dev);
+
+ return sprintf(buf, "%d\n", cable->active);
+}
+static DEVICE_ATTR_RO(active);
+
+static const char * const typec_plug_types[] = {
+ [USB_PLUG_NONE] = "unknown",
+ [USB_PLUG_TYPE_A] = "Type-A",
+ [USB_PLUG_TYPE_B] = "Type-B",
+ [USB_PLUG_TYPE_C] = "Type-C",
+ [USB_PLUG_CAPTIVE] = "Captive",
+};
+
+static ssize_t plug_type_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct typec_cable *cable = to_typec_cable(dev);
+
+ return sprintf(buf, "%s\n", typec_plug_types[cable->type]);
+}
+static DEVICE_ATTR_RO(plug_type);
+
+static struct attribute *typec_cable_attrs[] = {
+ &dev_attr_active.attr,
+ &dev_attr_plug_type.attr,
+ &dev_attr_usb_power_delivery_revision.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(typec_cable);
+
+static void typec_cable_release(struct device *dev)
+{
+ struct typec_cable *cable = to_typec_cable(dev);
+
+ kfree(cable);
+}
+
+static struct device_type typec_cable_dev_type = {
+ .name = "typec_cable_device",
+ .groups = typec_cable_groups,
+ .release = typec_cable_release,
+};
+
+struct typec_cable *typec_register_cable(struct typec_port *port,
+ struct typec_cable_desc *desc)
+{
+ struct typec_cable *cable = NULL;
+ int ret;
+
+ cable = kzalloc(sizeof(*cable), GFP_KERNEL);
+ if (!cable)
+ return NULL;
+
+ cable->type = desc->type;
+ cable->vdo = desc->vdo;
+ cable->active = desc->active;
+ cable->pd_revision = desc->pd_revision;
+
+ cable->dev.class = typec_class;
+ cable->dev.parent = &port->dev;
+ cable->dev.type = &typec_cable_dev_type;
+ dev_set_name(&cable->dev, "%s-cable", dev_name(&port->dev));
+
+ ret = device_register(&cable->dev);
+ if (ret) {
+ dev_err(&port->dev, "failed to register cable (%d)\n", ret);
+ put_device(&cable->dev);
+ kfree(cable);
+ return NULL;
+ }
+
+ return cable;
+}
+EXPORT_SYMBOL_GPL(typec_register_cable);
+
+void typec_unregister_cable(struct typec_cable *cable)
+{
+ if (cable)
+ device_unregister(&cable->dev);
+}
+EXPORT_SYMBOL_GPL(typec_unregister_cable);
+
+/* ------------------------------------------------------------------------- */
+/* USB Type-C ports */
+
+/* --------------------------------------- */
+/* Driver callbacks to report role updates */
+
+void typec_set_data_role(struct typec_port *port, enum typec_data_role role)
+{
+ if (port->data_role == role)
+ return;
+
+ port->data_role = role;
+ sysfs_notify(&port->dev.kobj, NULL, "current_data_role");
+ kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
+}
+EXPORT_SYMBOL_GPL(typec_set_data_role);
+
+void typec_set_pwr_role(struct typec_port *port, enum typec_role role)
+{
+ if (port->pwr_role == role)
+ return;
+
+ port->pwr_role = role;
+ sysfs_notify(&port->dev.kobj, NULL, "current_power_role");
+ kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
+}
+EXPORT_SYMBOL_GPL(typec_set_pwr_role);
+
+void typec_set_vconn_role(struct typec_port *port, enum typec_role role)
+{
+ if (port->vconn_role == role)
+ return;
+
+ port->vconn_role = role;
+ sysfs_notify(&port->dev.kobj, NULL, "current_vconn_role");
+ kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
+}
+EXPORT_SYMBOL_GPL(typec_set_vconn_role);
+
+void typec_set_pwr_opmode(struct typec_port *port,
+ enum typec_pwr_opmode opmode)
+{
+ if (port->pwr_opmode == opmode)
+ return;
+
+ port->pwr_opmode = opmode;
+ sysfs_notify(&port->dev.kobj, NULL, "power_operation_mode");
+ kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
+}
+EXPORT_SYMBOL_GPL(typec_set_pwr_opmode);
+
+/* --------------------------------------- */
+
+static const char * const typec_roles[] = {
+ [TYPEC_SINK] = "sink",
+ [TYPEC_SOURCE] = "source",
+};
+
+static const char * const typec_data_roles[] = {
+ [TYPEC_DEVICE] = "device",
+ [TYPEC_HOST] = "host",
+};
+
+static ssize_t
+preferred_role_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct typec_port *port = to_typec_port(dev);
+ int role;
+ int ret;
+
+ if (port->cap->type != TYPEC_PORT_DRP) {
+ dev_dbg(dev, "Preferred role only supported with DRP ports\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (!port->cap->try_role) {
+ dev_dbg(dev, "Setting preferred role not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ role = sysfs_match_string(typec_roles, buf);
+ if (role < 0) {
+ if (sysfs_streq(buf, "none"))
+ role = TYPEC_NO_PREFERRED_ROLE;
+ else
+ return -EINVAL;
+ }
+
+ ret = port->cap->try_role(port->cap, role);
+ if (ret)
+ return ret;
+
+ port->prefer_role = role;
+ return size;
+}
+
+static ssize_t
+preferred_role_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct typec_port *port = to_typec_port(dev);
+
+ if (port->cap->type != TYPEC_PORT_DRP)
+ return 0;
+
+ if (port->prefer_role < 0)
+ return 0;
+
+ return sprintf(buf, "%s\n", typec_roles[port->prefer_role]);
+}
+static DEVICE_ATTR_RW(preferred_role);
+
+static ssize_t
+current_data_role_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct typec_port *port = to_typec_port(dev);
+ int ret;
+
+ if (port->cap->type != TYPEC_PORT_DRP) {
+ dev_dbg(dev, "data role swap only supported with DRP ports\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (!port->cap->dr_set) {
+ dev_dbg(dev, "data role swapping not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ ret = sysfs_match_string(typec_data_roles, buf);
+ if (ret < 0)
+ return ret;
+
+ ret = port->cap->dr_set(port->cap, ret);
+ if (ret)
+ return ret;
+
+ return size;
+}
+
+static ssize_t
+current_data_role_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct typec_port *port = to_typec_port(dev);
+
+ return sprintf(buf, "%s\n", typec_data_roles[port->data_role]);
+}
+static DEVICE_ATTR_RW(current_data_role);
+
+static ssize_t supported_data_roles_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct typec_port *port = to_typec_port(dev);
+
+ if (port->cap->type == TYPEC_PORT_DRP)
+ return sprintf(buf, "host\ndevice\n");
+
+ return sprintf(buf, "%s\n", typec_data_roles[port->data_role]);
+}
+static DEVICE_ATTR_RO(supported_data_roles);
+
+static ssize_t current_power_role_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct typec_port *port = to_typec_port(dev);
+ int ret = size;
+
+ if (!port->cap->pd_revision) {
+ dev_dbg(dev, "power role swap only supported with USB PD\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (!port->cap->pr_set) {
+ dev_dbg(dev, "power role swapping not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (port->pwr_opmode != TYPEC_PWR_MODE_PD) {
+ dev_dbg(dev, "partner unable to swap power role\n");
+ return -EIO;
+ }
+
+ ret = sysfs_match_string(typec_roles, buf);
+ if (ret < 0)
+ return ret;
+
+ ret = port->cap->pr_set(port->cap, ret);
+ if (ret)
+ return ret;
+
+ return size;
+}
+
+static ssize_t current_power_role_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct typec_port *port = to_typec_port(dev);
+
+ return sprintf(buf, "%s\n", typec_roles[port->pwr_role]);
+}
+static DEVICE_ATTR_RW(current_power_role);
+
+static ssize_t supported_power_roles_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct typec_port *port = to_typec_port(dev);
+
+ if (port->cap->pd_revision || port->cap->type == TYPEC_PORT_DRP)
+ return sprintf(buf, "source\nsink\n");
+
+ return sprintf(buf, "%s\n", typec_roles[port->pwr_role]);
+}
+static DEVICE_ATTR_RO(supported_power_roles);
+
+static const char * const typec_pwr_opmodes[] = {
+ [TYPEC_PWR_MODE_USB] = "USB",
+ [TYPEC_PWR_MODE_1_5A] = "USB Type-C 1.5A",
+ [TYPEC_PWR_MODE_3_0A] = "USB Type-C 3.0A",
+ [TYPEC_PWR_MODE_PD] = "USB Power Delivery",
+};
+
+static ssize_t power_operation_mode_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct typec_port *port = to_typec_port(dev);
+
+ return sprintf(buf, "%s\n", typec_pwr_opmodes[port->pwr_opmode]);
+}
+static DEVICE_ATTR_RO(power_operation_mode);
+
+static ssize_t vconn_source_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct typec_port *port = to_typec_port(dev);
+ enum typec_role role;
+ int ret;
+
+ if (!port->cap->pd_revision) {
+ dev_dbg(dev, "vconn swap only supported with USB PD\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (!port->cap->vconn_set) {
+ dev_dbg(dev, "vconn swapping not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (sysfs_streq(buf, "1"))
+ role = TYPEC_SOURCE;
+ else if (sysfs_streq(buf, "0"))
+ role = TYPEC_SINK;
+ else
+ return -EINVAL;
+
+ ret = port->cap->vconn_set(port->cap, role);
+ if (ret)
+ return ret;
+
+ return size;
+}
+
+static ssize_t vconn_source_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct typec_port *port = to_typec_port(dev);
+
+ return sprintf(buf, "%d\n", port->vconn_role == TYPEC_SOURCE ? 1 : 0);
+}
+static DEVICE_ATTR_RW(vconn_source);
+
+static ssize_t supported_accessory_modes_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct typec_port *port = to_typec_port(dev);
+ ssize_t ret = 0;
+ int i;
+
+ if (!port->cap->accessory)
+ return 0;
+
+ for (i = 0; port->cap->accessory[i]; i++)
+ ret += sprintf(buf + ret, "%s\n",
+ typec_accessory_modes[port->cap->accessory[i]]);
+ return ret;
+}
+static DEVICE_ATTR_RO(supported_accessory_modes);
+
+static ssize_t usb_typec_revision_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct typec_port *port = to_typec_port(dev);
+ u16 rev = port->cap->revision;
+
+ return sprintf(buf, "%d.%d\n", (rev >> 8) & 0xff, rev >> 4 & 0xf);
+}
+static DEVICE_ATTR_RO(usb_typec_revision);
+
+static struct attribute *typec_attrs[] = {
+ &dev_attr_current_power_role.attr,
+ &dev_attr_current_data_role.attr,
+ &dev_attr_power_operation_mode.attr,
+ &dev_attr_preferred_role.attr,
+ &dev_attr_supported_accessory_modes.attr,
+ &dev_attr_supported_data_roles.attr,
+ &dev_attr_supported_power_roles.attr,
+ &dev_attr_usb_power_delivery_revision.attr,
+ &dev_attr_usb_typec_revision.attr,
+ &dev_attr_vconn_source.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(typec);
+
+static int typec_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ int ret;
+
+ ret = add_uevent_var(env, "TYPEC_PORT=%s", dev_name(dev));
+ if (ret)
+ dev_err(dev, "failed to add uevent TYPEC_PORT\n");
+
+ return ret;
+}
+
+static void typec_release(struct device *dev)
+{
+ struct typec_port *port = to_typec_port(dev);
+
+ ida_simple_remove(&typec_index_ida, port->id);
+ kfree(port);
+}
+
+static struct device_type typec_port_dev_type = {
+ .name = "typec_port",
+ .groups = typec_groups,
+ .uevent = typec_uevent,
+ .release = typec_release,
+};
+
+struct typec_altmode
+*typec_port_register_altmode(struct typec_port *port,
+ struct typec_altmode_desc *desc)
+{
+ return typec_register_altmode(&port->dev, desc);
+}
+EXPORT_SYMBOL_GPL(typec_port_register_altmode);
+
+struct typec_port *typec_register_port(struct device *parent,
+ const struct typec_capability *cap)
+{
+ struct typec_port *port;
+ enum typec_role role;
+ int ret;
+ int id;
+
+ port = kzalloc(sizeof(*port), GFP_KERNEL);
+ if (!port)
+ return NULL;
+
+ id = ida_simple_get(&typec_index_ida, 0, 0, GFP_KERNEL);
+ if (id < 0) {
+ kfree(port);
+ return NULL;
+ }
+
+ if (cap->type == TYPEC_PORT_DFP)
+ role = TYPEC_SOURCE;
+ else if (cap->type == TYPEC_PORT_UFP)
+ role = TYPEC_SINK;
+ else
+ role = cap->prefer_role;
+
+ if (role == TYPEC_SOURCE) {
+ port->data_role = TYPEC_HOST;
+ port->pwr_role = TYPEC_SOURCE;
+ port->vconn_role = TYPEC_SOURCE;
+ } else {
+ port->data_role = TYPEC_DEVICE;
+ port->pwr_role = TYPEC_SINK;
+ port->vconn_role = TYPEC_SINK;
+ }
+
+ port->id = id;
+ port->cap = cap;
+ port->prefer_role = cap->prefer_role;
+
+ port->dev.type = &typec_port_dev_type;
+ port->dev.class = typec_class;
+ port->dev.parent = parent;
+ dev_set_name(&port->dev, "port%d", id);
+
+ ret = device_register(&port->dev);
+ if (ret) {
+ dev_err(parent, "failed to register port (%d)\n", ret);
+ ida_simple_remove(&typec_index_ida, id);
+ put_device(&port->dev);
+ kfree(port);
+ return NULL;
+ }
+
+ return port;
+}
+EXPORT_SYMBOL_GPL(typec_register_port);
+
+void typec_unregister_port(struct typec_port *port)
+{
+ if (port)
+ device_unregister(&port->dev);
+}
+EXPORT_SYMBOL_GPL(typec_unregister_port);
+
+static int __init typec_init(void)
+{
+ typec_class = class_create(THIS_MODULE, "typec");
+ if (IS_ERR(typec_class))
+ return PTR_ERR(typec_class);
+ return 0;
+}
+subsys_initcall(typec_init);
+
+static void __exit typec_exit(void)
+{
+ class_destroy(typec_class);
+ ida_destroy(&typec_index_ida);
+}
+module_exit(typec_exit);
+
+MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("USB Type-C Connector Class");
diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
new file mode 100644
index 0000000..37b996c
--- /dev/null
+++ b/include/linux/usb/typec.h
@@ -0,0 +1,212 @@
+
+#ifndef __LINUX_USB_TYPEC_H
+#define __LINUX_USB_TYPEC_H
+
+#include <linux/types.h>
+
+/* USB Type-C Specification releases */
+#define USB_TYPEC_REV_1_0 0x100 /* 1.0 */
+#define USB_TYPEC_REV_1_1 0x110 /* 1.1 */
+#define USB_TYPEC_REV_1_2 0x120 /* 1.2 */
+
+struct typec_altmode;
+struct typec_partner;
+struct typec_cable;
+struct typec_plug;
+struct typec_port;
+
+enum typec_port_type {
+ TYPEC_PORT_DFP,
+ TYPEC_PORT_UFP,
+ TYPEC_PORT_DRP,
+};
+
+enum typec_plug_type {
+ USB_PLUG_NONE,
+ USB_PLUG_TYPE_A,
+ USB_PLUG_TYPE_B,
+ USB_PLUG_TYPE_C,
+ USB_PLUG_CAPTIVE,
+};
+
+enum typec_data_role {
+ TYPEC_DEVICE,
+ TYPEC_HOST,
+};
+
+enum typec_role {
+ TYPEC_SINK,
+ TYPEC_SOURCE,
+};
+
+enum typec_pwr_opmode {
+ TYPEC_PWR_MODE_USB,
+ TYPEC_PWR_MODE_1_5A,
+ TYPEC_PWR_MODE_3_0A,
+ TYPEC_PWR_MODE_PD,
+};
+
+enum typec_accessory {
+ TYPEC_ACCESSORY_NONE,
+ TYPEC_ACCESSORY_AUDIO,
+ TYPEC_ACCESSORY_DEBUG,
+};
+
+/*
+ * struct typec_mode_desc - Individual Mode of an Alternate Mode
+ * @index: Index of the Mode within the SVID
+ * @vdo: VDO returned by Discover Modes USB PD command
+ * @desc: Optional human readable description of the mode
+ * @roles: Only for ports. DRP if the mode is available in both roles
+ *
+ * Description of a mode of an Alternate Mode which a connector, cable plug or
+ * partner supports. Every mode will have it's own sysfs group. The details are
+ * the VDO returned by discover modes command, description for the mode and
+ * active flag telling has the mode being entered or not.
+ */
+struct typec_mode_desc {
+ int index;
+ u32 vdo;
+ char *desc;
+ /* Only used with ports */
+ enum typec_port_type roles;
+};
+
+/*
+ * struct typec_altmode_desc - USB Type-C Alternate Mode Descriptor
+ * @svid: Standard or Vendor ID
+ * @n_modes: Number of modes
+ * @modes: Array of modes supported by the Alternate Mode
+ *
+ * Representation of an Alternate Mode that has SVID assigned by USB-IF. The
+ * array of modes will list the modes of a particular SVID that are supported by
+ * a connector, partner of a cable plug.
+ */
+struct typec_altmode_desc {
+ u16 svid;
+ int n_modes;
+ struct typec_mode_desc *modes;
+};
+
+struct typec_altmode
+*typec_partner_register_altmode(struct typec_partner *partner,
+ struct typec_altmode_desc *desc);
+struct typec_altmode
+*typec_plug_register_altmode(struct typec_plug *plug,
+ struct typec_altmode_desc *desc);
+struct typec_altmode
+*typec_port_register_altmode(struct typec_port *port,
+ struct typec_altmode_desc *desc);
+void typec_unregister_altmode(struct typec_altmode *altmode);
+
+struct typec_port *typec_altmode2port(struct typec_altmode *alt);
+
+void typec_altmode_update_active(struct typec_altmode *alt, int mode,
+ bool active);
+
+enum typec_plug_index {
+ TYPEC_PLUG_SOP_P,
+ TYPEC_PLUG_SOP_PP,
+};
+
+/*
+ * struct typec_plug_desc - USB Type-C Cable Plug Descriptor
+ * @index: SOP Prime for the plug connected to DFP and SOP Double Prime for the
+ * plug connected to UFP
+ *
+ * Represents USB Type-C Cable Plug.
+ */
+struct typec_plug_desc {
+ enum typec_plug_index index;
+};
+
+/*
+ * struct typec_cable_desc - USB Type-C Cable Descriptor
+ * @pd_revision: USB Power Delivery Specification revision
+ * @type: The plug type from USB PD Cable VDO
+ * @active: Is the cable active or passive
+ *
+ * Represents USB Type-C Cable attached to USB Type-C port.
+ */
+struct typec_cable_desc {
+ u16 pd_revision; /* 0300H = "3.0" */
+ enum typec_plug_type type;
+ u32 vdo;
+ unsigned int active:1;
+};
+
+/*
+ * struct typec_partner_desc - USB Type-C Partner Descriptor
+ * @pd_revision: USB Power Delivery Specification revision (0 = no USB PD)
+ * @vdo: VDO returned by Discover Identity USB PD command
+ * @accessory: Audio, Debug or none.
+ *
+ * Details about a partner that is attached to USB Type-C port.
+ */
+struct typec_partner_desc {
+ u16 pd_revision; /* 0300H = "3.0" */
+ u32 vdo;
+ enum typec_accessory accessory;
+};
+
+/*
+ * struct typec_capability - USB Type-C Port Capabilities
+ * @role: DFP (Host-only), UFP (Device-only) or DRP (Dual Role)
+ * @revision: USB Type-C Specification release. Binary coded decimal
+ * @pd_revision: USB Power Delivery Specification revision if supported
+ * @prefer_role: Initial role preference
+ * @accessory: Supported Accessory Modes (NULL terminated array)
+ * @try_role: Set data role preference for DRP port
+ * @dr_set: Set Data Role
+ * @pr_set: Set Power Role
+ * @vconn_set: Set VCONN Role
+ * @activate_mode: Enter/exit given Alternate Mode
+ *
+ * Static capabilities of a single USB Type-C port.
+ */
+struct typec_capability {
+ enum typec_port_type type;
+ u16 revision; /* 0120H = "1.2" */
+ u16 pd_revision; /* 0300H = "3.0" */
+ int prefer_role;
+ enum typec_accessory *accessory;
+
+ int (*try_role)(const struct typec_capability *,
+ int role);
+
+ int (*dr_set)(const struct typec_capability *,
+ enum typec_data_role);
+ int (*pr_set)(const struct typec_capability *,
+ enum typec_role);
+ int (*vconn_set)(const struct typec_capability *,
+ enum typec_role);
+
+ int (*activate_mode)(const struct typec_capability *,
+ int mode, int activate);
+};
+
+/* Specific to try_role(). Indicates the user want's to clear the preference. */
+#define TYPEC_NO_PREFERRED_ROLE (-1)
+
+struct typec_port *typec_register_port(struct device *parent,
+ const struct typec_capability *cap);
+void typec_unregister_port(struct typec_port *port);
+
+struct typec_partner *typec_register_partner(struct typec_port *port,
+ struct typec_partner_desc *desc);
+void typec_unregister_partner(struct typec_partner *partner);
+
+struct typec_cable *typec_register_cable(struct typec_port *port,
+ struct typec_cable_desc *desc);
+void typec_unregister_cable(struct typec_cable *cable);
+
+struct typec_plug *typec_register_plug(struct typec_cable *cable,
+ struct typec_plug_desc *desc);
+void typec_unregister_plug(struct typec_plug *plug);
+
+void typec_set_data_role(struct typec_port *port, enum typec_data_role role);
+void typec_set_pwr_role(struct typec_port *port, enum typec_role role);
+void typec_set_vconn_role(struct typec_port *port, enum typec_role role);
+void typec_set_pwr_opmode(struct typec_port *port, enum typec_pwr_opmode mode);
+
+#endif /* __LINUX_USB_TYPEC_H */