* Re: [PATCHv12 2/3] usb: USB Type-C connector class
2016-11-22 14:11 ` [PATCHv12 2/3] usb: USB Type-C connector class Heikki Krogerus
@ 2016-11-23 16:27 ` Guenter Roeck
2016-11-24 0:17 ` Guenter Roeck
2016-11-24 5:12 ` Guenter Roeck
2 siblings, 0 replies; 20+ messages in thread
From: Guenter Roeck @ 2016-11-23 16:27 UTC (permalink / raw)
To: Heikki Krogerus
Cc: Greg KH, Badhri Jagan Sridharan, Oliver Neukum, linux-kernel, linux-usb
On Tue, Nov 22, 2016 at 04:11:46PM +0200, Heikki Krogerus wrote:
> The purpose of USB Type-C connector class is to provide
> unified interface for the user space to get the status and
> basic information about USB Type-C connectors on a system,
> control over data role swapping, and when the port supports
> USB Power Delivery, also control over power role swapping
> and Alternate Modes.
>
> Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Just a couple of nitpicks, otherwise
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Guenter
> ---
> Documentation/ABI/testing/sysfs-class-typec | 222 ++++++
> 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 | 1013 +++++++++++++++++++++++++++
> include/linux/usb/typec.h | 252 +++++++
> 9 files changed, 1611 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..4fac77c
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-class-typec
> @@ -0,0 +1,222 @@
> +USB Type-C port devices (eg. /sys/class/typec/usbc0/)
> +
> +What: /sys/class/typec/<port>/current_data_role
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + The current USB data role the port is operating in. This
> + attribute can be used for requesting data role swapping on the
> + port. Swapping is supported as synchronous operation, so
> + write(2) to the attribute will not return until the operation
> + has finished. The attribute is notified about role changes so
> + that poll(2) on the attribute wakes up. Change on the role will
> + also generate uevent KOBJ_CHANGE on the port.
> +
> + Valid values:
> + - host
> + - device
> +
> +What: /sys/class/typec/<port>/current_power_role
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + The current power role of the port. This attribute can be used
> + to request power role swap on the port when the port supports
> + USB Power Delivery. Swapping is supported as synchronous
> + operation, so write(2) to the attribute will not return until
> + the operation has finished. The attribute is notified about role
> + changes so that poll(2) on the attribute wakes up. Change on the
> + role will also generate uevent KOBJ_CHANGE.
> +
> + Valid values:
> + - source
> + - sink
> +
> +What: /sys/class/typec/<port>/vconn_source
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Shows is the port VCONN Source. This attribute can be used to
> + request VCONN swap to change the VCONN Source during connection
> + when both the port and the partner support USB Power Delivery.
> + Swapping is supported as synchronous operation, so write(2) to
> + the attribute will not return until the operation has finished.
> + The attribute is notified about VCONN source changes so that
> + poll(2) on the attribute wakes up. Change on VCONN source also
> + generates uevent KOBJ_CHANGE.
> +
> + Valid values are:
> + - 0 when the port is not the VCONN Source
> + - 1 when the port is the VCONN Source
> +
> +What: /sys/class/typec/<port>/power_operation_mode
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Shows the current power operational mode the port is in.
> +
> + Valid values:
> + - USB - Normal power levels defined in USB specifications
> + - BC1.2 - Power levels defined in Battery Charging Specification
> + v1.2
This does not match the code. Maybe add later if it is ever added, or add it
now to the code ?
> + - 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
Nitpick: Uses spaces instead of tabs, causing it to look unaligned
> + Power Delivery specification
> +
> +What: /sys/class/typec/<port>/preferred_role
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + The user space can notify the driver about the preferred role.
> + It should be handled as enabling of Try.SRC or Try.SNK, as
> + defined in USB Type-C specification, in the port drivers. By
> + default there is no preferred role.
> +
> + Valid values:
> + - source
> + - sink
> + - none - to remove preference
> +
> +What: /sys/class/typec/<port>/supported_accessory_modes
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Lists the Accessory Modes, defined in the USB Type-C
> + specification, the port supports.
> +
> +What: /sys/class/typec/<port>/supported_data_roles
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Lists the USB data roles the port is capable of supporting.
> +
> + Valid values:
> + - device
> + - host
> + - device, host (DRD as defined in USB Type-C specification v1.2)
> +
> +What: /sys/class/typec/<port>/supported_power_roles
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Lists the power roles the port is capable of supporting.
> +
> + Valid values:
> + - source
> + - sink
> + - source, sink (DRP as defined in USB Type-C specification v1.2)
> +
> +What: /sys/class/typec/<port>/supports_usb_power_delivery
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Shows if the port supports USB Power Delivery.
> + - 1 if USB Power Delivery is supported
> + - 0 when it's not
> +
> +
> +USB Type-C partner devices (eg. /sys/class/typec/usbc0-partner/)
> +
> +What: /sys/class/typec/<port>-partner/accessory_mode
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Shows the Accessory Mode name, or "no" when the partner does not
> + support Accesory Modes. The Accessory Modes are defined in USB
> + Type-C Specification.
> +
> +What: /sys/class/typec/<port>-partner/supports_usb_power_delivery
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Shows if the partner supports USB Power Delivery:
> + - 0 when USB Power Delivery is not supported
> + - 1 when USB Power Delivery is supported
> +
> +
> +USB Type-C cable devices (eg. /sys/class/typec/usbc0-cable/)
> +
> +Note: Electronically Marked Cables will have a device also for one cable plug
> +(eg. /sys/class/typec/usbc0-plug0). If the cable is active and has also SOP
> +Double Prime controller (USB Power Deliver specification ch. 2.4) it will have
> +second device also for the other plug. Both plugs may have their alternate modes
> +as described in USB Type-C and USB Power Delivery specifications.
> +
> +What: /sys/class/typec/<port>-cable/active
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Shows if the cable is active or passive.
> +
> + Valid values:
> + - 0 when the cable is passive
> + - 1 when the cable is active
> +
> +What: /sys/class/typec/<port>-cable/plug_type
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Shows type of the plug on the cable:
> + - Type-A - Standard A
> + - Type-B - Standard B
> + - Type-C - USB Type-C
> + - Captive - Non-standard
> +
> +What: /sys/class/typec/<port>-cable/supports_usb_power_delivery
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Shows if the cable supports USB Power Delivery:
> + - 0 when USB Power Delivery is not supported
> + - 1 when USB Power Delivery is supported
> +
> +
> +Alternate Mode devices (For example,
> +/sys/class/typec/usbc0-partner/usbc0-partner.svid:xxxx/). The ports, partners
> +and cable plugs can have alternate modes.
> +
> +What: /sys/class/typec/<dev>/<dev>.svid:<svid>/<mode>/active
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Shows if the mode is active or not. The attribute can be used
> + for entering/exiting the mode with partners and cable plugs, and
> + with the port alternate modes it can be used for disabling
> + support for specific alternate modes. Entering/exiting modes is
> + supported as synchronous operation so write(2) to the attribute
> + does not return until the enter/exit mode operation has
> + finished. The attribute is notified when the mode is
> + entered/exited so poll(2) on the attribute wakes up.
> + Entering/exiting a mode will also generate uevent KOBJ_CHANGE.
> +
> + Valid values:
> + - 0 when the mode is deactive
> + - 1 when the mode is active
> +
> +What: /sys/class/typec/<dev>/<dev>.svid:<svid>/<mode>/description
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Shows description of the mode. The description is optional for
> + the drivers, just like with the Billboard Devices.
> +
> +What: /sys/class/typec/<dev>/<dev>.svid:<svid>/<mode>/vdo
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Shows the VDO in hexadecimal returned from the Discover Modes
> + command.
> +
> +What: /sys/class/typec/<port>/<port>.svid:<svid>/<mode>/supported_roles
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Shows the roles, source or sink, the mode is supported with.
> +
> + This attribute is available for the devices describing the
> + alternate modes a port supports, and it will not be exposed with
> + the devices presenting the alternate modes the partners or cable
> + plugs support.
> diff --git a/Documentation/usb/typec.txt b/Documentation/usb/typec.txt
> new file mode 100644
> index 0000000..7095a2a
> --- /dev/null
> +++ b/Documentation/usb/typec.txt
> @@ -0,0 +1,103 @@
> +USB Type-C connector class
> +==========================
> +
> +Introduction
> +------------
> +The typec class is meant for describing the USB Type-C ports in a system to the
> +user space in unified fashion. The class is designed to provide nothing else
> +except the user space interface implementation in hope that it can be utilized
> +on as many platforms as possible.
> +
> +The platforms are expected to register every USB Type-C port they have with the
> +class. In a normal case the registration will be done by a USB Type-C or PD PHY
> +driver, but it may be a driver for firmware interface such as UCSI, driver for
> +USB PD controller or even driver for Thunderbolt3 controller. This document
> +considers the component registering the USB Type-C ports with the class as "port
> +driver".
> +
> +On top of showing the capabilities, the class also offer the user space control
> +over the roles and alternate modes they support when the port driver is capable
> +of supporting those features.
> +
> +The class provides an API for the port drivers described in this document. The
> +attributes are described in Documentation/ABI/testing/sysfs-class-typec.
> +
> +
> +Interface
> +---------
> +Every port will be presented as its own device under /sys/class/typec/. The
> +first port will be named "usbc0", the second "usbc1" and so on.
> +
> +When connected, the partner will be presented also as its own device under
> +/sys/class/typec/. The parent of the partner device will always be the port. The
> +partner attached to port "usbc0" will be named "usbc0-partner". Full path to the
> +device would be /sys/class/typec/usb0/usb0-partner/.
> +
> +The cable and the two plugs on it may also be optionally presented as their own
> +devices under /sys/class/typec/. The cable attached to the port "usbc0" port
> +will be named usbc0-cable and the plug on the SOP Prime end (see USB Power
> +Delivery Specification ch. 2.4) will be named "usbc-plug0" and on the SOP Double
> +Prime end "usbc0-plug1". The parent of a cable will always be the port, and the
> +parent of the cable plugs will always be the cable.
> +
> +If the port, partner or cable plug support Alternate Modes, every Alternate Mode
> +SVID will have their own device describing them. The Alternate Modes will not be
> +attached to the typec class. For the port's "usbc0" partner, the Alternate Modes
> +would have devices presented under /sys/class/typec/usbc0-partner/. Every mode
> +that is supported will have its own group under the Alternate Mode device named
> +"mode<id>". For example /sys/class/typec/usbc0/usbc0.svid:xxxx/mode0/. The
> +requests for entering/exiting the modes happens with the "active" attribute in
> +that group.
> +
> +
> +API
> +---
> +
> +* Registering the ports
> +
> +The port drivers will describe every Type-C port they control with struct
> +typec_capability data structure, and register them with the following API:
> +
> +struct typec_port *typec_register_port(struct device *dev,
> + const struct typec_capability *cap);
> +
> +The class will provide handle to struct typec_port on success and ERR_PTR on
> +failure. The un-registration of the port happens with the following API:
> +
> +void typec_unregister_port(struct typec_port *port);
> +
> +
> +* Notifications
> +
> +When connection happens on a port, the port driver fills struct typec_connection
> +which is passed to the class. The class provides the following API for reporting
> +connection/disconnection:
> +
> +int typec_connect(struct typec_port *port, struct typec_connection *);
> +void typec_disconnect(struct typec_port *);
> +
> +When the partner end has executed a role change, the port driver uses the
> +following APIs to report it to the class:
> +
> +void typec_set_data_role(struct typec_port *, enum typec_data_role);
> +void typec_set_pwr_role(struct typec_port *, enum typec_role);
> +void typec_set_vconn_role(struct typec_port *, enum typec_role);
> +void typec_set_pwr_opmode(struct typec_port *, enum typec_pwr_opmode);
> +
> +
> +* Alternate Modes
> +
> +After connection, the port drivers register the alternate modes the partner
> +and/or cable plugs support. And before reporting disconnection, the port driver
> +_must_ unregister all the alternate modes registered for the partner and cable
> +plugs. The API takes the struct device of the partner or the cable plug as
> +parameter:
> +
> +int typec_register_altmodes(struct device *, struct typec_altmode *);
> +void typec_unregister_altmodes(struct device *);
> +
> +When the partner end enters or exits the modes, the port driver needs to notify
> +the class with the following API:
> +
> +void typec_altmode_update_active(struct typec_altmode *alt, int mode,
> + bool active);
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 5e25ba1..baa2322 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -12761,6 +12761,15 @@ F: drivers/usb/
> F: include/linux/usb.h
> F: include/linux/usb/
>
> +USB TYPEC SUBSYSTEM
> +M: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +L: linux-usb@vger.kernel.org
> +S: Maintained
> +F: Documentation/ABI/testing/sysfs-class-typec
> +F: Documentation/usb/typec.txt
> +F: drivers/usb/typec/
> +F: include/linux/usb/typec.h
> +
> USB UHCI DRIVER
> M: Alan Stern <stern@rowland.harvard.edu>
> L: linux-usb@vger.kernel.org
> diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
> index fbe493d..89c322e 100644
> --- a/drivers/usb/Kconfig
> +++ b/drivers/usb/Kconfig
> @@ -152,6 +152,8 @@ source "drivers/usb/phy/Kconfig"
>
> source "drivers/usb/gadget/Kconfig"
>
> +source "drivers/usb/typec/Kconfig"
> +
> config USB_LED_TRIG
> bool "USB LED Triggers"
> depends on LEDS_CLASS && LEDS_TRIGGERS
> diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
> index 7791af6..c7f4098 100644
> --- a/drivers/usb/Makefile
> +++ b/drivers/usb/Makefile
> @@ -62,3 +62,5 @@ obj-$(CONFIG_USB_GADGET) += gadget/
> obj-$(CONFIG_USB_COMMON) += common/
>
> obj-$(CONFIG_USBIP_CORE) += usbip/
> +
> +obj-$(CONFIG_TYPEC) += typec/
> diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
> new file mode 100644
> index 0000000..17792f9
> --- /dev/null
> +++ b/drivers/usb/typec/Kconfig
> @@ -0,0 +1,7 @@
> +
> +menu "USB Power Delivery and Type-C drivers"
> +
> +config TYPEC
> + tristate
> +
> +endmenu
> diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
> new file mode 100644
> index 0000000..1012a8b
> --- /dev/null
> +++ b/drivers/usb/typec/Makefile
> @@ -0,0 +1 @@
> +obj-$(CONFIG_TYPEC) += typec.o
> diff --git a/drivers/usb/typec/typec.c b/drivers/usb/typec/typec.c
> new file mode 100644
> index 0000000..d6063e2
> --- /dev/null
> +++ b/drivers/usb/typec/typec.c
> @@ -0,0 +1,1013 @@
> +/*
> + * USB Type-C Connector Class
> + *
> + * Copyright (C) 2016, Intel Corporation
> + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/device.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/usb/typec.h>
> +
> +struct typec_port {
> + unsigned int id;
> + struct device dev;
> +
> + int prefer_role;
> +
> + enum typec_data_role data_role;
> + enum typec_role pwr_role;
> + enum typec_role vconn_role;
> + enum typec_pwr_opmode pwr_opmode;
> +
> + struct typec_partner *partner;
> + struct typec_cable *cable;
> +
> + unsigned int connected:1;
> +
> + const struct typec_capability *cap;
> +};
> +
> +#define to_typec_port(p) container_of(p, struct typec_port, dev)
> +#define to_typec_plug(p) container_of(p, struct typec_plug, dev)
> +#define to_typec_cable(p) container_of(p, struct typec_cable, dev)
> +#define to_typec_partner(p) container_of(p, struct typec_partner, dev)
> +
> +static DEFINE_IDA(typec_index_ida);
> +
> +static struct class typec_class = {
> + .name = "typec",
> +};
> +
> +static const char * const typec_accessory_modes[] = {
> + [TYPEC_ACCESSORY_NONE] = "no",
> + [TYPEC_ACCESSORY_AUDIO] = "Audio Adapter Accessory Mode",
> + [TYPEC_ACCESSORY_DEBUG] = "Debug Accessory Mode",
> +};
> +
> +static struct device_type typec_partner_dev_type;
> +static struct device_type typec_cable_dev_type;
> +static struct device_type typec_port_dev_type;
> +
> +static ssize_t supports_usb_power_delivery_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + if (dev->type == &typec_partner_dev_type) {
> + struct typec_partner *p = to_typec_partner(dev);
> +
> + return sprintf(buf, "%d\n", p->usb_pd);
> + } else if (dev->type == &typec_partner_dev_type) {
> + struct typec_cable *p = to_typec_cable(dev);
> +
> + return sprintf(buf, "%d\n", p->usb_pd);
> + } else {
> + struct typec_port *p = to_typec_port(dev);
> +
> + return sprintf(buf, "%d\n", p->cap->usb_pd);
> + }
> +}
> +static DEVICE_ATTR_RO(supports_usb_power_delivery);
> +
> +/* ------------------------------------------------------------------------- */
> +/* Type-C Partners */
> +
> +static ssize_t accessory_mode_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct typec_partner *p = to_typec_partner(dev);
> +
> + return sprintf(buf, "%s\n", typec_accessory_modes[p->accessory]);
> +}
> +static DEVICE_ATTR_RO(accessory_mode);
> +
> +static struct attribute *typec_partner_attrs[] = {
> + &dev_attr_accessory_mode.attr,
> + &dev_attr_supports_usb_power_delivery.attr,
> + NULL
> +};
> +ATTRIBUTE_GROUPS(typec_partner);
> +
> +static void typec_partner_release(struct device *dev)
> +{
> + struct typec_port *port = to_typec_port(dev->parent);
> +
> + port->partner = NULL;
> +}
> +
> +static struct device_type typec_partner_dev_type = {
> + .name = "typec_partner_device",
> + .groups = typec_partner_groups,
> + .release = typec_partner_release,
> +};
> +
> +static int
> +typec_add_partner(struct typec_port *port, struct typec_partner *partner)
> +{
> + struct device *dev = &partner->dev;
> + int ret;
> +
> + dev->class = &typec_class;
> + dev->parent = &port->dev;
> + dev->type = &typec_partner_dev_type;
> + dev_set_name(dev, "%s-partner", dev_name(&port->dev));
> +
> + ret = device_register(dev);
> + if (ret) {
> + put_device(dev);
> + return ret;
> + }
> +
> + port->partner = partner;
> + return 0;
> +}
> +
> +static void typec_remove_partner(struct typec_port *port)
> +{
> + device_unregister(&port->partner->dev);
> +}
> +
> +/* ------------------------------------------------------------------------- */
> +/* Type-C Cable Plugs */
> +
> +static void typec_plug_release(struct device *dev)
> +{
> + struct typec_plug *plug = to_typec_plug(dev);
> +
> + memset(plug, 0, sizeof(*plug));
> +}
> +
> +static struct device_type typec_plug_dev_type = {
> + .name = "typec_plug_device",
> + .release = typec_plug_release,
> +};
> +
> +static int
> +typec_add_plug(struct typec_port *port, struct typec_plug *plug)
> +{
> + struct device *dev = &plug->dev;
> + char name[8];
> + int ret;
> +
> + sprintf(name, "plug%d", plug->index);
> +
> + dev->class = &typec_class;
> + dev->parent = &port->cable->dev;
> + dev->type = &typec_plug_dev_type;
> + dev_set_name(dev, "%s-%s", dev_name(&port->dev), name);
> +
> + ret = device_register(dev);
> + if (ret) {
> + put_device(dev);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void typec_remove_plug(struct typec_plug *plug)
> +{
> + device_unregister(&plug->dev);
> +}
> +
> +/* Type-C Cables */
> +
> +static ssize_t
> +active_show(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> + struct typec_cable *cable = to_typec_cable(dev);
> +
> + return sprintf(buf, "%d\n", cable->active);
> +}
> +static DEVICE_ATTR_RO(active);
> +
> +static const char * const typec_plug_types[] = {
> + [USB_PLUG_NONE] = "unknown",
> + [USB_PLUG_TYPE_A] = "Type-A",
> + [USB_PLUG_TYPE_B] = "Type-B",
> + [USB_PLUG_TYPE_C] = "Type-C",
> + [USB_PLUG_CAPTIVE] = "Captive",
> +};
> +
> +static ssize_t plug_type_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct typec_cable *cable = to_typec_cable(dev);
> +
> + return sprintf(buf, "%s\n", typec_plug_types[cable->type]);
> +}
> +static DEVICE_ATTR_RO(plug_type);
> +
> +static struct attribute *typec_cable_attrs[] = {
> + &dev_attr_active.attr,
> + &dev_attr_plug_type.attr,
> + &dev_attr_supports_usb_power_delivery.attr,
> + NULL
> +};
> +ATTRIBUTE_GROUPS(typec_cable);
> +
> +static void typec_cable_release(struct device *dev)
> +{
> + struct typec_port *port = to_typec_port(dev->parent);
> +
> + port->cable = NULL;
> +}
> +
> +static struct device_type typec_cable_dev_type = {
> + .name = "typec_cable_device",
> + .groups = typec_cable_groups,
> + .release = typec_cable_release,
> +};
> +
> +static int typec_add_cable(struct typec_port *port, struct typec_cable *cable)
> +{
> + struct device *dev = &cable->dev;
> + int ret;
> +
> + dev->class = &typec_class;
> + dev->parent = &port->dev;
> + dev->type = &typec_cable_dev_type;
> + dev_set_name(dev, "%s-cable", dev_name(&port->dev));
> +
> + ret = device_register(dev);
> + if (ret) {
> + put_device(dev);
> + return ret;
> + }
> +
> + /* Plug1 */
> + if (!cable->usb_pd)
> + return 0;
> +
> + cable->plug[0].index = 1;
> + ret = typec_add_plug(port, &cable->plug[0]);
> + if (ret) {
> + device_unregister(dev);
> + return ret;
> + }
> +
> + /* Plug2 */
> + if (!cable->active || !cable->sop_pp_controller)
> + return 0;
> +
> + cable->plug[1].index = 2;
> + ret = typec_add_plug(port, &cable->plug[1]);
> + if (ret) {
> + typec_remove_plug(&cable->plug[0]);
> + device_unregister(dev);
> + return ret;
> + }
> +
> + port->cable = cable;
> + return 0;
> +}
> +
> +static void typec_remove_cable(struct typec_port *port)
> +{
> + if (port->cable->active) {
> + typec_remove_plug(&port->cable->plug[0]);
> + if (port->cable->sop_pp_controller)
> + typec_remove_plug(&port->cable->plug[1]);
> + }
> + device_unregister(&port->cable->dev);
> +}
> +
> +/* ------------------------------------------------------------------------- */
> +/* API for the port drivers */
> +
> +static void typec_init_roles(struct typec_port *port)
> +{
> + if (port->prefer_role < 0)
> + return;
> +
> + if (port->prefer_role == TYPEC_SOURCE) {
> + port->data_role = TYPEC_HOST;
> + port->pwr_role = TYPEC_SOURCE;
> + port->vconn_role = TYPEC_SOURCE;
> + } else {
> + /* Device mode as default also by default with DRP ports */
> + port->data_role = TYPEC_DEVICE;
> + port->pwr_role = TYPEC_SINK;
> + port->vconn_role = TYPEC_SINK;
> + }
> +}
> +
> +int typec_connect(struct typec_port *port, struct typec_connection *con)
> +{
> + int ret;
> +
> + if (!con->partner && !con->cable)
> + return -EINVAL;
> +
> + port->connected = 1;
> + port->data_role = con->data_role;
> + port->pwr_role = con->pwr_role;
> + port->vconn_role = con->vconn_role;
> + port->pwr_opmode = con->pwr_opmode;
> +
> + if (con->cable) {
> + ret = typec_add_cable(port, con->cable);
> + if (ret)
> + return ret;
> + }
> +
> + if (con->partner) {
> + ret = typec_add_partner(port, con->partner);
> + if (ret) {
> + if (con->cable)
> + typec_remove_cable(port);
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(typec_connect);
> +
> +void typec_disconnect(struct typec_port *port)
> +{
> + if (port->partner)
> + typec_remove_partner(port);
> +
> + if (port->cable)
> + typec_remove_cable(port);
> +
> + port->connected = 0;
> +
> + port->pwr_opmode = TYPEC_PWR_MODE_USB;
> +
> + typec_init_roles(port);
> +}
> +EXPORT_SYMBOL_GPL(typec_disconnect);
> +
> +/* --------------------------------------- */
> +/* Driver callbacks to report role updates */
> +
> +void typec_set_data_role(struct typec_port *port, enum typec_data_role role)
> +{
> + port->data_role = role;
> + sysfs_notify(&port->dev.kobj, NULL, "current_data_role");
> + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
> +}
> +EXPORT_SYMBOL_GPL(typec_set_data_role);
> +
> +void typec_set_pwr_role(struct typec_port *port, enum typec_role role)
> +{
> + port->pwr_role = role;
> + sysfs_notify(&port->dev.kobj, NULL, "current_power_role");
> + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
> +}
> +EXPORT_SYMBOL_GPL(typec_set_pwr_role);
> +
> +void typec_set_vconn_role(struct typec_port *port, enum typec_role role)
> +{
> + port->vconn_role = role;
> + sysfs_notify(&port->dev.kobj, NULL, "current_vconn_role");
> + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
> +}
> +EXPORT_SYMBOL_GPL(typec_set_vconn_role);
> +
> +void typec_set_pwr_opmode(struct typec_port *port,
> + enum typec_pwr_opmode opmode)
> +{
> + port->pwr_opmode = opmode;
> + sysfs_notify(&port->dev.kobj, NULL, "power_operation_mode");
> + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
> +}
> +EXPORT_SYMBOL_GPL(typec_set_pwr_opmode);
> +
> +/* -------------------------------- */
> +/* Alternate Modes */
> +
> +/*
> + * typec_altmode_update_active - Notify about Enter/Exit mode
> + * @alt: Handle to the Alternate Mode
> + * @mode: Mode id
> + * @active: True when the mode has been enterred
> + */
> +void typec_altmode_update_active(struct typec_altmode *alt, int mode,
> + bool active)
> +{
> + struct typec_mode *m = alt->modes + mode;
> + char dir[6];
> +
> + m->active = active;
> + snprintf(dir, 6, "mode%d", mode);
> + sysfs_notify(&alt->dev.kobj, dir, "active");
> + kobject_uevent(&alt->dev.kobj, KOBJ_CHANGE);
> +}
> +EXPORT_SYMBOL_GPL(typec_altmode_update_active);
> +
> +/*
> + * typec_altmode2port - Alternate Mode to USB Type-C port
> + * @alt: The Alternate Mode
> + *
> + * Returns the port that the cable plug or partner with @alt is connected to.
> + */
> +struct typec_port *typec_altmode2port(struct typec_altmode *alt)
> +{
> + if (alt->dev.parent->type == &typec_plug_dev_type)
> + return to_typec_port(alt->dev.parent->parent->parent);
> + if (alt->dev.parent->type == &typec_partner_dev_type)
> + return to_typec_port(alt->dev.parent->parent);
> + if (alt->dev.parent->type == &typec_port_dev_type)
> + return to_typec_port(alt->dev.parent);
> +
> + return NULL;
> +}
> +EXPORT_SYMBOL_GPL(typec_altmode2port);
> +
> +static void typec_altmode_release(struct device *dev)
> +{
> + struct typec_altmode *alt = to_altmode(dev);
> +
> + kfree(alt->mode_groups);
> +}
> +
> +static ssize_t
> +typec_altmode_vdo_show(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + struct typec_mode *mode = container_of(attr, struct typec_mode,
> + vdo_attr);
> +
> + return sprintf(buf, "0x%08x\n", mode->vdo);
> +}
> +
> +static ssize_t
> +typec_altmode_desc_show(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + struct typec_mode *mode = container_of(attr, struct typec_mode,
> + desc_attr);
> +
> + return sprintf(buf, "%s\n", mode->desc ? mode->desc : "");
> +}
> +
> +static ssize_t
> +typec_altmode_active_show(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + struct typec_mode *mode = container_of(attr, struct typec_mode,
> + active_attr);
> +
> + return sprintf(buf, "%d\n", mode->active);
> +}
> +
> +static ssize_t
> +typec_altmode_active_store(struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t size)
> +{
> + struct typec_mode *mode = container_of(attr, struct typec_mode,
> + active_attr);
> + struct typec_port *port = typec_altmode2port(mode->alt_mode);
> + bool activate;
> + int ret;
> +
> + if (!port->cap->activate_mode)
> + return -EOPNOTSUPP;
> +
> + ret = kstrtobool(buf, &activate);
> + if (ret)
> + return ret;
> +
> + ret = port->cap->activate_mode(mode->alt_mode, mode->index, activate);
> + if (ret)
> + return ret;
> +
> + return size;
> +}
> +
> +static ssize_t
> +typec_altmode_roles_show(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + struct typec_mode *mode = container_of(attr, struct typec_mode,
> + roles_attr);
> + ssize_t ret;
> +
> + switch (mode->roles) {
> + case TYPEC_PORT_DFP:
> + ret = sprintf(buf, "source\n");
> + break;
> + case TYPEC_PORT_UFP:
> + ret = sprintf(buf, "sink\n");
> + break;
> + case TYPEC_PORT_DRP:
> + default:
> + ret = sprintf(buf, "source, sink\n");
> + break;
> + }
> + return ret;
> +}
> +
> +static void typec_init_modes(struct typec_altmode *alt, int is_port)
> +{
> + struct typec_mode *mode = alt->modes;
> + int i;
> +
> + for (i = 0; i < alt->n_modes; i++, mode++) {
> + mode->alt_mode = alt;
> + mode->index = i;
> + sprintf(mode->group_name, "mode%d", i);
> +
> + sysfs_attr_init(&mode->vdo_attr.attr);
> + mode->vdo_attr.attr.name = "vdo";
> + mode->vdo_attr.attr.mode = 0444;
> + mode->vdo_attr.show = typec_altmode_vdo_show;
> +
> + sysfs_attr_init(&mode->desc_attr.attr);
> + mode->desc_attr.attr.name = "description";
> + mode->desc_attr.attr.mode = 0444;
> + mode->desc_attr.show = typec_altmode_desc_show;
> +
> + sysfs_attr_init(&mode->active_attr.attr);
> + mode->active_attr.attr.name = "active";
> + mode->active_attr.attr.mode = 0644;
> + mode->active_attr.show = typec_altmode_active_show;
> + mode->active_attr.store = typec_altmode_active_store;
> +
> + mode->attrs[0] = &mode->vdo_attr.attr;
> + mode->attrs[1] = &mode->desc_attr.attr;
> + mode->attrs[2] = &mode->active_attr.attr;
> +
> + /* With ports, list the roles that the mode is supported with */
> + if (is_port) {
> + sysfs_attr_init(&mode->roles_attr.attr);
> + mode->roles_attr.attr.name = "supported_roles";
> + mode->roles_attr.attr.mode = 0444;
> + mode->roles_attr.show = typec_altmode_roles_show;
> +
> + mode->attrs[3] = &mode->roles_attr.attr;
> + }
> +
> + mode->group.attrs = mode->attrs;
> + mode->group.name = mode->group_name;
> +
> + alt->mode_groups[i] = &mode->group;
> + }
> +}
> +
> +static int
> +typec_add_altmode(struct device *parent, struct typec_altmode *alt, int is_port)
> +{
> + struct device *dev = &alt->dev;
> + int ret;
> +
> + alt->mode_groups = kcalloc(alt->n_modes + 1,
> + sizeof(struct attribute_group *),
> + GFP_KERNEL);
> + if (!alt->mode_groups)
> + return -ENOMEM;
> +
> + typec_init_modes(alt, is_port);
> +
> + dev->groups = alt->mode_groups;
> + dev->release = typec_altmode_release;
> + dev->parent = parent;
> + /* TODO: if (!is_port) dev->bus = &typec_altmode_bus; */
> +
> + dev_set_name(dev, "%s.svid:%04x", dev_name(parent), alt->svid);
> +
> + ret = device_register(dev);
> + if (ret) {
> + put_device(dev);
> + kfree(alt->mode_groups);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int __typec_register_altmodes(struct device *parent,
> + struct typec_altmode *alt_modes,
> + int is_port)
> +{
> + struct typec_altmode *alt;
> + int index;
> + int ret;
> +
> + if (!alt_modes)
> + return 0;
> +
> + for (alt = alt_modes, index = 0; alt->svid; alt++, index++) {
> + ret = typec_add_altmode(parent, alt, is_port);
> + if (ret)
> + goto err;
> + }
> +
> + return 0;
> +err:
> + for (alt = alt_modes + index; index; alt--, index--)
> + device_unregister(&alt->dev);
> +
> + return ret;
> +}
> +
> +int typec_register_altmodes(struct device *dev, struct typec_altmode *alt_modes)
> +{
> + if (dev->type == &typec_partner_dev_type) {
> + struct typec_partner *p = to_typec_partner(dev);
> +
> + p->alt_modes = alt_modes;
> + } else if (dev->type == &typec_plug_dev_type) {
> + struct typec_plug *p = to_typec_plug(dev);
> +
> + p->alt_modes = alt_modes;
> + } else {
> + return -ENODEV;
> + }
> +
> + return __typec_register_altmodes(dev, alt_modes, false);
> +}
> +EXPORT_SYMBOL_GPL(typec_register_altmodes);
> +
> +void typec_unregister_altmodes(struct device *dev)
> +{
> + struct typec_altmode *alt_modes = NULL;
> + struct typec_altmode *alt;
> +
> + if (dev->type == &typec_partner_dev_type) {
> + struct typec_partner *p = to_typec_partner(dev);
> +
> + alt_modes = p->alt_modes;
> + p->alt_modes = NULL;
> + } else if (dev->type == &typec_plug_dev_type) {
> + struct typec_plug *p = to_typec_plug(dev);
> +
> + alt_modes = p->alt_modes;
> + p->alt_modes = NULL;
> + }
> +
> + if (!alt_modes)
> + return;
> +
> + for (alt = alt_modes; alt->svid; alt++)
> + device_unregister(&alt->dev);
> +}
> +EXPORT_SYMBOL_GPL(typec_unregister_altmodes);
> +
> +/* ------------------------------------------------------------------------- */
> +/* USB Type-C ports */
> +
> +static const char * const typec_roles[] = {
> + [TYPEC_SINK] = "sink",
> + [TYPEC_SOURCE] = "source",
> +};
> +
> +static const char * const typec_data_roles[] = {
> + [TYPEC_DEVICE] = "device",
> + [TYPEC_HOST] = "host",
> +};
> +
> +static ssize_t
> +preferred_role_store(struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t size)
> +{
> + struct typec_port *port = to_typec_port(dev);
> + int role;
> + int ret;
> +
> + if (port->cap->type != TYPEC_PORT_DRP) {
> + dev_dbg(dev, "Preferred role only supported with DRP ports\n");
> + return -EOPNOTSUPP;
> + }
> +
> + if (!port->cap->try_role) {
> + dev_dbg(dev, "Setting preferred role not supported\n");
> + return -EOPNOTSUPP;
> + }
> +
> + role = sysfs_match_string(typec_roles, buf);
> + if (role < 0) {
> + if (sysfs_streq(buf, "none"))
> + role = TYPEC_NO_PREFERRED_ROLE;
> + else
> + return -EINVAL;
> + }
> +
> + ret = port->cap->try_role(port->cap, role);
> + if (ret)
> + return ret;
> +
> + port->prefer_role = role;
> + return size;
> +}
> +
> +static ssize_t
> +preferred_role_show(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + struct typec_port *port = to_typec_port(dev);
> +
> + if (port->prefer_role < 0)
> + return 0;
> +
> + return sprintf(buf, "%s\n", typec_roles[port->prefer_role]);
> +}
> +static DEVICE_ATTR_RW(preferred_role);
> +
> +static ssize_t
> +current_data_role_store(struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t size)
> +{
> + struct typec_port *port = to_typec_port(dev);
> + int ret;
> +
> + if (port->cap->type != TYPEC_PORT_DRP) {
> + dev_dbg(dev, "data role swap only supported with DRP ports\n");
> + return -EOPNOTSUPP;
> + }
> +
> + if (!port->cap->dr_set) {
> + dev_dbg(dev, "data role swapping not supported\n");
> + return -EOPNOTSUPP;
> + }
> +
> + ret = sysfs_match_string(typec_data_roles, buf);
> + if (ret < 0)
> + return ret;
> +
> + ret = port->cap->dr_set(port->cap, ret);
> + if (ret)
> + return ret;
> +
> + return size;
> +}
> +
> +static ssize_t
> +current_data_role_show(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + struct typec_port *port = to_typec_port(dev);
> +
> + return sprintf(buf, "%s\n", typec_data_roles[port->data_role]);
> +}
> +static DEVICE_ATTR_RW(current_data_role);
> +
> +static ssize_t supported_data_roles_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct typec_port *port = to_typec_port(dev);
> +
> + if (port->cap->type == TYPEC_PORT_DRP)
> + return sprintf(buf, "host, device\n");
> +
> + return sprintf(buf, "%s\n", typec_data_roles[port->data_role]);
> +}
> +static DEVICE_ATTR_RO(supported_data_roles);
> +
> +static ssize_t current_power_role_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t size)
> +{
> + struct typec_port *port = to_typec_port(dev);
> + int ret = size;
> +
> + if (!port->cap->usb_pd) {
> + dev_dbg(dev, "power role swap only supported with USB PD\n");
> + return -EOPNOTSUPP;
> + }
> +
> + if (!port->cap->pr_set) {
> + dev_dbg(dev, "power role swapping not supported\n");
> + return -EOPNOTSUPP;
> + }
> +
> + if (port->pwr_opmode != TYPEC_PWR_MODE_PD) {
> + dev_dbg(dev, "partner unable to swap power role\n");
> + return -EIO;
> + }
> +
> + ret = sysfs_match_string(typec_roles, buf);
> + if (ret < 0)
> + return ret;
> +
> + ret = port->cap->pr_set(port->cap, ret);
> + if (ret)
> + return ret;
> +
> + return size;
> +}
> +
> +static ssize_t current_power_role_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct typec_port *port = to_typec_port(dev);
> +
> + return sprintf(buf, "%s\n", typec_roles[port->pwr_role]);
> +}
> +static DEVICE_ATTR_RW(current_power_role);
> +
> +static ssize_t supported_power_roles_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct typec_port *port = to_typec_port(dev);
> +
> + if (port->cap->usb_pd || port->cap->type == TYPEC_PORT_DRP)
> + return sprintf(buf, "source, sink\n");
> +
> + return sprintf(buf, "%s\n", typec_roles[port->pwr_role]);
> +}
> +static DEVICE_ATTR_RO(supported_power_roles);
> +
> +static const char * const typec_pwr_opmodes[] = {
> + [TYPEC_PWR_MODE_USB] = "USB",
> + [TYPEC_PWR_MODE_1_5A] = "USB Type-C 1.5A",
> + [TYPEC_PWR_MODE_3_0A] = "USB Type-C 3.0A",
> + [TYPEC_PWR_MODE_PD] = "USB Power Delivery",
> +};
> +
> +static ssize_t power_operation_mode_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct typec_port *port = to_typec_port(dev);
> +
> + return sprintf(buf, "%s\n", typec_pwr_opmodes[port->pwr_opmode]);
> +}
> +static DEVICE_ATTR_RO(power_operation_mode);
> +
> +static ssize_t vconn_source_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t size)
> +{
> + struct typec_port *port = to_typec_port(dev);
> + enum typec_role role;
> + int ret;
> +
> + if (!port->cap->usb_pd) {
> + dev_dbg(dev, "vconn swap only supported with USB PD\n");
> + return -EOPNOTSUPP;
> + }
> +
> + if (!port->cap->vconn_set) {
> + dev_dbg(dev, "vconn swapping not supported\n");
> + return -EOPNOTSUPP;
> + }
> +
> + if (sysfs_streq(buf, "1"))
> + role = TYPEC_SOURCE;
> + else if (sysfs_streq(buf, "0"))
> + role = TYPEC_SINK;
> + else
> + return -EINVAL;
> +
> + ret = port->cap->vconn_set(port->cap, role);
> + if (ret)
> + return ret;
> +
> + return size;
> +}
> +
> +static ssize_t vconn_source_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct typec_port *port = to_typec_port(dev);
> +
> + return sprintf(buf, "%d\n", port->vconn_role == TYPEC_SOURCE ? 1 : 0);
> +}
> +static DEVICE_ATTR_RW(vconn_source);
> +
> +static ssize_t supported_accessory_modes_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct typec_port *port = to_typec_port(dev);
> + ssize_t ret = 0;
> + int i;
> +
> + if (!port->cap->accessory)
> + return 0;
> +
> + for (i = 0; port->cap->accessory[i]; i++)
> + ret += sprintf(buf + ret, "%s\n",
> + typec_accessory_modes[port->cap->accessory[i]]);
> + return ret;
> +}
> +static DEVICE_ATTR_RO(supported_accessory_modes);
> +
> +static struct attribute *typec_attrs[] = {
> + &dev_attr_current_power_role.attr,
> + &dev_attr_current_data_role.attr,
> + &dev_attr_power_operation_mode.attr,
> + &dev_attr_preferred_role.attr,
> + &dev_attr_supported_accessory_modes.attr,
> + &dev_attr_supported_data_roles.attr,
> + &dev_attr_supported_power_roles.attr,
> + &dev_attr_supports_usb_power_delivery.attr,
> + &dev_attr_vconn_source.attr,
> + NULL,
> +};
> +ATTRIBUTE_GROUPS(typec);
> +
> +static int typec_uevent(struct device *dev, struct kobj_uevent_env *env)
> +{
> + int ret;
> +
> + ret = add_uevent_var(env, "TYPEC_PORT=%s", dev_name(dev));
> + if (ret)
> + dev_err(dev, "failed to add uevent TYPEC_PORT\n");
> +
> + return ret;
> +}
> +
> +static void typec_release(struct device *dev)
> +{
> + struct typec_port *port = to_typec_port(dev);
> +
> + ida_simple_remove(&typec_index_ida, port->id);
> + kfree(port);
> +}
> +
> +static struct device_type typec_port_dev_type = {
> + .name = "typec_port",
> + .groups = typec_groups,
> + .uevent = typec_uevent,
> + .release = typec_release,
> +};
> +
> +struct typec_port *typec_register_port(struct device *dev,
> + const struct typec_capability *cap)
> +{
> + struct typec_port *port;
> + int ret;
> + int id;
> +
> + port = kzalloc(sizeof(*port), GFP_KERNEL);
> + if (!port)
> + return ERR_PTR(-ENOMEM);
> +
> + id = ida_simple_get(&typec_index_ida, 0, 0, GFP_KERNEL);
> + if (id < 0) {
> + kfree(port);
> + return ERR_PTR(id);
> + }
> +
> + port->prefer_role = TYPEC_NO_PREFERRED_ROLE;
> +
> + port->id = id;
> + port->cap = cap;
> + port->dev.type = &typec_port_dev_type;
> + port->dev.class = &typec_class;
> + port->dev.parent = dev;
> + dev_set_name(&port->dev, "usbc%d", id);
> +
> + typec_init_roles(port);
> +
> + ret = device_register(&port->dev);
> + if (ret)
> + goto reg_err;
> +
> + ret = __typec_register_altmodes(&port->dev, cap->alt_modes, true);
> + if (ret)
> + goto alt_err;
> +
> + return port;
> +alt_err:
> + device_unregister(&port->dev);
> +reg_err:
> + ida_simple_remove(&typec_index_ida, id);
> + put_device(&port->dev);
> + kfree(port);
> + return ERR_PTR(ret);
> +}
> +EXPORT_SYMBOL_GPL(typec_register_port);
> +
> +void typec_unregister_port(struct typec_port *port)
> +{
> + struct typec_altmode *alt;
> +
> + WARN_ON(port->connected);
> +
> + if (port->cap->alt_modes)
> + for (alt = port->cap->alt_modes; alt->svid; alt++)
> + device_unregister(&alt->dev);
> + device_unregister(&port->dev);
> +}
> +EXPORT_SYMBOL_GPL(typec_unregister_port);
> +
> +static int __init typec_init(void)
> +{
> + return class_register(&typec_class);
> +}
> +subsys_initcall(typec_init);
> +
> +static void __exit typec_exit(void)
> +{
> + ida_destroy(&typec_index_ida);
> + class_unregister(&typec_class);
> +}
> +module_exit(typec_exit);
> +
> +MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>");
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("USB Type-C Connector Class");
> diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
> new file mode 100644
> index 0000000..31ea45b
> --- /dev/null
> +++ b/include/linux/usb/typec.h
> @@ -0,0 +1,252 @@
> +
> +#ifndef __LINUX_USB_TYPEC_H
> +#define __LINUX_USB_TYPEC_H
> +
> +#include <linux/device.h>
> +#include <linux/types.h>
> +
> +struct typec_port;
> +
> +enum typec_port_type {
> + TYPEC_PORT_DFP,
> + TYPEC_PORT_UFP,
> + TYPEC_PORT_DRP,
> +};
> +
> +enum typec_plug_type {
> + USB_PLUG_NONE,
> + USB_PLUG_TYPE_A,
> + USB_PLUG_TYPE_B,
> + USB_PLUG_TYPE_C,
> + USB_PLUG_CAPTIVE,
> +};
> +
> +enum typec_data_role {
> + TYPEC_DEVICE,
> + TYPEC_HOST,
> +};
> +
> +enum typec_role {
> + TYPEC_SINK,
> + TYPEC_SOURCE,
> +};
> +
> +enum typec_pwr_opmode {
> + TYPEC_PWR_MODE_USB,
> + TYPEC_PWR_MODE_1_5A,
> + TYPEC_PWR_MODE_3_0A,
> + TYPEC_PWR_MODE_PD,
> +};
> +
> +enum typec_accessory {
> + TYPEC_ACCESSORY_NONE,
> + TYPEC_ACCESSORY_AUDIO,
> + TYPEC_ACCESSORY_DEBUG,
> +};
> +
> +/*
> + * struct typec_mode - Individual Mode of an Alternate Mode
> + * @vdo: VDO returned by Discover Modes USB PD command
> + * @desc: Mode description
> + * @active: Tells if the mode is currently entered or not
> + * @index: Index of the mode
> + * @group_name: Name for the sysfs folder in form "mode<index>"
> + * @group: The sysfs group (folder) for the mode
> + * @attrs: The attributes for the sysfs group
> + * @vdo_attr: Device attribute to expose the VDO of the mode
> + * @desc_attr: Device attribute to expose the description of the mode
> + * @active_attr: Device attribute to expose active of the mode
> + * @roles: Only for ports. DRP if the mode is awailable in both roles
> + * @roles_attr: Device attribute, only for ports, to expose the supported roles
> + *
> + * Details about a mode of an Alternate Mode which a connector, cable plug or
> + * partner supports. Every mode will have it's own sysfs group. The details are
> + * the VDO returned by discover modes command, description for the mode and
> + * active flag telling is the mode currently active or not.
> + */
> +struct typec_mode {
> + u32 vdo;
> + char *desc;
> + unsigned int active:1;
> + /* Only for ports */
> + enum typec_port_type roles;
> +
> + struct typec_altmode *alt_mode;
> +
> + int index;
> + char group_name[8];
> + struct attribute_group group;
> + struct attribute *attrs[5];
> + struct device_attribute vdo_attr;
> + struct device_attribute desc_attr;
> + struct device_attribute active_attr;
> + /* Only for ports */
> + struct device_attribute roles_attr;
> +};
> +
> +/*
> + * struct typec_altmode - USB Type-C Alternate Mode
> + * @dev: struct device instance
> + * @name: Name for the Alternate Mode (optional)
> + * @svid: Standard or Vendor ID
> + * @n_modes: Number of modes
> + * @modes: Array of modes supported by the Alternat Mode
> + * @mode_groups: The modes as attribute groups to be exposed in sysfs
> + *
> + * Representation of an Alternate Mode that has SVID assigned by USB-IF. The
> + * array of modes will list the modes of a particular SVID that are supported by
> + * a connector, partner of a cable plug.
> + */
> +struct typec_altmode {
> + struct device dev;
> + char *name;
> +
> + u16 svid;
> + int n_modes;
> + struct typec_mode *modes;
> +
> + const struct attribute_group **mode_groups;
> +};
> +
> +#define to_altmode(d) container_of(d, struct typec_altmode, dev)
> +
> +struct typec_port *typec_altmode2port(struct typec_altmode *alt);
> +
> +void typec_altmode_update_active(struct typec_altmode *alt, int mode,
> + bool active);
> +
> +int typec_register_altmodes(struct device *dev, struct typec_altmode *alt);
> +void typec_unregister_altmodes(struct device *dev);
> +
> +/*
> + * struct typec_plug - USB Type-C Cable Plug
> + * @dev: struct device instance
> + * @index: 1 for the plug connected to DFP and 2 for the plug connected to UFP
> + * @alt_modes: Alternate Modes the cable plug supports (null terminated)
> + *
> + * Represents USB Type-C Cable Plug.
> + */
> +struct typec_plug {
> + struct device dev;
> + int index;
> + struct typec_altmode *alt_modes;
> +};
> +
> +/*
> + * struct typec_cable - USB Type-C Cable
> + * @dev: struct device instance
> + * @type: The plug type from USB PD Cable VDO
> + * @usb_pd: Electronically Marked Cable
> + * @active: Is the cable active or passive
> + * @sop_pp_controller: Tells whether both cable plugs are configurable or not
> + * @plug: The two plugs in the cable.
> + *
> + * Represents USB Type-C Cable attached to USB Type-C port. Two plugs are
> + * created if the cable has SOP Double Prime controller as defined in USB PD
> + * specification. Otherwise only one will be created if the cable is active. For
> + * passive cables no plugs are created.
> + */
> +struct typec_cable {
> + struct device dev;
> + enum typec_plug_type type;
> + u32 vdo;
> + unsigned int usb_pd:1;
> + unsigned int active:1;
> + unsigned int sop_pp_controller:1;
> +
> + struct typec_plug plug[2];
> +};
> +
> +/*
> + * struct typec_partner - USB Type-C Partner
> + * @dev: struct device instance
> + * @type: Normal USB device, charger, Alternate Mode or Accessory
> + * @usb_pd: USB Power Delivery support
> + * @vdo: VDO returned by Discover Identity USB PD command
> + * @alt_modes: Alternate Modes the partner supports (null terminated)
> + *
> + * Details about a partner that is attached to USB Type-C port.
> + */
> +struct typec_partner {
> + struct device dev;
> + unsigned int usb_pd:1;
> + u32 vdo;
> + enum typec_accessory accessory;
> + struct typec_altmode *alt_modes;
> +};
> +
> +/*
> + * struct typec_capability - USB Type-C Port Capabilities
> + * @role: DFP (Host-only), UFP (Device-only) or DRP (Dual Role)
> + * @usb_pd: USB Power Delivery support
> + * @accessory: Supported Accessory Modes (NULL terminated array)
> + * @alt_modes: Alternate Modes the connector supports (NULL terminated)
> + * @try_role: Set data role preference for DRP port
> + * @dr_set: Set Data Role
> + * @pr_set: Set Power Role
> + * @vconn_set: Set VCONN Role
> + * @activate_mode: Enter/exit given Alternate Mode
> + *
> + * Static capabilities of a single USB Type-C port.
> + */
> +struct typec_capability {
> + enum typec_port_type type;
> + unsigned int usb_pd:1;
> + enum typec_accessory *accessory;
> + struct typec_altmode *alt_modes;
> +
> + int (*try_role)(const struct typec_capability *,
> + int role);
> +
> + int (*dr_set)(const struct typec_capability *,
> + enum typec_data_role);
> + int (*pr_set)(const struct typec_capability *,
> + enum typec_role);
> + int (*vconn_set)(const struct typec_capability *,
> + enum typec_role);
> +
> + int (*activate_mode)(struct typec_altmode *,
> + int mode, int activate);
> +};
> +
> +/* Specific to try_role(). Indicates the user want's to clear the preference. */
> +#define TYPEC_NO_PREFERRED_ROLE (-1)
> +
> +/*
> + * struct typec_connection - Details about USB Type-C port connection event
> + * @partner: The attached partner
> + * @cable: The attached cable
> + * @data_role: Initial USB data role (host or device)
> + * @pwr_role: Initial Power role (source or sink)
> + * @vconn_role: Initial VCONN role (source or sink)
> + * @pwr_opmode: The power mode of the connection
> + *
> + * All the relevant details about a connection event. Wrapper that is passed to
> + * typec_connect(). The context is copied when typec_connect() is called and the
> + * structure is not used for anything else.
> + */
> +struct typec_connection {
> + struct typec_partner *partner;
> + struct typec_cable *cable;
> +
> + enum typec_data_role data_role;
> + enum typec_role pwr_role;
> + enum typec_role vconn_role;
> + enum typec_pwr_opmode pwr_opmode;
> +};
> +
> +struct typec_port *typec_register_port(struct device *dev,
> + const struct typec_capability *cap);
> +void typec_unregister_port(struct typec_port *port);
> +
> +int typec_connect(struct typec_port *port, struct typec_connection *con);
> +void typec_disconnect(struct typec_port *port);
> +
> +/* Callbacks from driver */
> +
> +void typec_set_data_role(struct typec_port *port, enum typec_data_role role);
> +void typec_set_pwr_role(struct typec_port *port, enum typec_role role);
> +void typec_set_vconn_role(struct typec_port *port, enum typec_role role);
> +void typec_set_pwr_opmode(struct typec_port *port, enum typec_pwr_opmode mode);
> +
> +#endif /* __LINUX_USB_TYPEC_H */
> --
> 2.10.2
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCHv12 2/3] usb: USB Type-C connector class
2016-11-22 14:11 ` [PATCHv12 2/3] usb: USB Type-C connector class Heikki Krogerus
2016-11-23 16:27 ` Guenter Roeck
@ 2016-11-24 0:17 ` Guenter Roeck
2016-11-24 5:12 ` Guenter Roeck
2 siblings, 0 replies; 20+ messages in thread
From: Guenter Roeck @ 2016-11-24 0:17 UTC (permalink / raw)
To: Heikki Krogerus
Cc: Greg KH, Badhri Jagan Sridharan, Oliver Neukum, linux-kernel, linux-usb
On Tue, Nov 22, 2016 at 04:11:46PM +0200, Heikki Krogerus wrote:
> The purpose of USB Type-C connector class is to provide
> unified interface for the user space to get the status and
> basic information about USB Type-C connectors on a system,
> control over data role swapping, and when the port supports
> USB Power Delivery, also control over power role swapping
> and Alternate Modes.
>
> Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Tested-by: Guenter Roeck <linux@roeck-us.net>
> ---
> Documentation/ABI/testing/sysfs-class-typec | 222 ++++++
> 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 | 1013 +++++++++++++++++++++++++++
> include/linux/usb/typec.h | 252 +++++++
> 9 files changed, 1611 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..4fac77c
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-class-typec
> @@ -0,0 +1,222 @@
> +USB Type-C port devices (eg. /sys/class/typec/usbc0/)
> +
> +What: /sys/class/typec/<port>/current_data_role
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + The current USB data role the port is operating in. This
> + attribute can be used for requesting data role swapping on the
> + port. Swapping is supported as synchronous operation, so
> + write(2) to the attribute will not return until the operation
> + has finished. The attribute is notified about role changes so
> + that poll(2) on the attribute wakes up. Change on the role will
> + also generate uevent KOBJ_CHANGE on the port.
> +
> + Valid values:
> + - host
> + - device
> +
> +What: /sys/class/typec/<port>/current_power_role
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + The current power role of the port. This attribute can be used
> + to request power role swap on the port when the port supports
> + USB Power Delivery. Swapping is supported as synchronous
> + operation, so write(2) to the attribute will not return until
> + the operation has finished. The attribute is notified about role
> + changes so that poll(2) on the attribute wakes up. Change on the
> + role will also generate uevent KOBJ_CHANGE.
> +
> + Valid values:
> + - source
> + - sink
> +
> +What: /sys/class/typec/<port>/vconn_source
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Shows is the port VCONN Source. This attribute can be used to
> + request VCONN swap to change the VCONN Source during connection
> + when both the port and the partner support USB Power Delivery.
> + Swapping is supported as synchronous operation, so write(2) to
> + the attribute will not return until the operation has finished.
> + The attribute is notified about VCONN source changes so that
> + poll(2) on the attribute wakes up. Change on VCONN source also
> + generates uevent KOBJ_CHANGE.
> +
> + Valid values are:
> + - 0 when the port is not the VCONN Source
> + - 1 when the port is the VCONN Source
> +
> +What: /sys/class/typec/<port>/power_operation_mode
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Shows the current power operational mode the port is in.
> +
> + Valid values:
> + - USB - Normal power levels defined in USB specifications
> + - 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: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + The user space can notify the driver about the preferred role.
> + It should be handled as enabling of Try.SRC or Try.SNK, as
> + defined in USB Type-C specification, in the port drivers. By
> + default there is no preferred role.
> +
> + Valid values:
> + - source
> + - sink
> + - none - to remove preference
> +
> +What: /sys/class/typec/<port>/supported_accessory_modes
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Lists the Accessory Modes, defined in the USB Type-C
> + specification, the port supports.
> +
> +What: /sys/class/typec/<port>/supported_data_roles
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Lists the USB data roles the port is capable of supporting.
> +
> + Valid values:
> + - device
> + - host
> + - device, host (DRD as defined in USB Type-C specification v1.2)
> +
> +What: /sys/class/typec/<port>/supported_power_roles
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Lists the power roles the port is capable of supporting.
> +
> + Valid values:
> + - source
> + - sink
> + - source, sink (DRP as defined in USB Type-C specification v1.2)
> +
> +What: /sys/class/typec/<port>/supports_usb_power_delivery
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Shows if the port supports USB Power Delivery.
> + - 1 if USB Power Delivery is supported
> + - 0 when it's not
> +
> +
> +USB Type-C partner devices (eg. /sys/class/typec/usbc0-partner/)
> +
> +What: /sys/class/typec/<port>-partner/accessory_mode
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Shows the Accessory Mode name, or "no" when the partner does not
> + support Accesory Modes. The Accessory Modes are defined in USB
> + Type-C Specification.
> +
> +What: /sys/class/typec/<port>-partner/supports_usb_power_delivery
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Shows if the partner supports USB Power Delivery:
> + - 0 when USB Power Delivery is not supported
> + - 1 when USB Power Delivery is supported
> +
> +
> +USB Type-C cable devices (eg. /sys/class/typec/usbc0-cable/)
> +
> +Note: Electronically Marked Cables will have a device also for one cable plug
> +(eg. /sys/class/typec/usbc0-plug0). If the cable is active and has also SOP
> +Double Prime controller (USB Power Deliver specification ch. 2.4) it will have
> +second device also for the other plug. Both plugs may have their alternate modes
> +as described in USB Type-C and USB Power Delivery specifications.
> +
> +What: /sys/class/typec/<port>-cable/active
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Shows if the cable is active or passive.
> +
> + Valid values:
> + - 0 when the cable is passive
> + - 1 when the cable is active
> +
> +What: /sys/class/typec/<port>-cable/plug_type
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Shows type of the plug on the cable:
> + - Type-A - Standard A
> + - Type-B - Standard B
> + - Type-C - USB Type-C
> + - Captive - Non-standard
> +
> +What: /sys/class/typec/<port>-cable/supports_usb_power_delivery
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Shows if the cable supports USB Power Delivery:
> + - 0 when USB Power Delivery is not supported
> + - 1 when USB Power Delivery is supported
> +
> +
> +Alternate Mode devices (For example,
> +/sys/class/typec/usbc0-partner/usbc0-partner.svid:xxxx/). The ports, partners
> +and cable plugs can have alternate modes.
> +
> +What: /sys/class/typec/<dev>/<dev>.svid:<svid>/<mode>/active
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Shows if the mode is active or not. The attribute can be used
> + for entering/exiting the mode with partners and cable plugs, and
> + with the port alternate modes it can be used for disabling
> + support for specific alternate modes. Entering/exiting modes is
> + supported as synchronous operation so write(2) to the attribute
> + does not return until the enter/exit mode operation has
> + finished. The attribute is notified when the mode is
> + entered/exited so poll(2) on the attribute wakes up.
> + Entering/exiting a mode will also generate uevent KOBJ_CHANGE.
> +
> + Valid values:
> + - 0 when the mode is deactive
> + - 1 when the mode is active
> +
> +What: /sys/class/typec/<dev>/<dev>.svid:<svid>/<mode>/description
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Shows description of the mode. The description is optional for
> + the drivers, just like with the Billboard Devices.
> +
> +What: /sys/class/typec/<dev>/<dev>.svid:<svid>/<mode>/vdo
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Shows the VDO in hexadecimal returned from the Discover Modes
> + command.
> +
> +What: /sys/class/typec/<port>/<port>.svid:<svid>/<mode>/supported_roles
> +Date: December 2016
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Shows the roles, source or sink, the mode is supported with.
> +
> + This attribute is available for the devices describing the
> + alternate modes a port supports, and it will not be exposed with
> + the devices presenting the alternate modes the partners or cable
> + plugs support.
> diff --git a/Documentation/usb/typec.txt b/Documentation/usb/typec.txt
> new file mode 100644
> index 0000000..7095a2a
> --- /dev/null
> +++ b/Documentation/usb/typec.txt
> @@ -0,0 +1,103 @@
> +USB Type-C connector class
> +==========================
> +
> +Introduction
> +------------
> +The typec class is meant for describing the USB Type-C ports in a system to the
> +user space in unified fashion. The class is designed to provide nothing else
> +except the user space interface implementation in hope that it can be utilized
> +on as many platforms as possible.
> +
> +The platforms are expected to register every USB Type-C port they have with the
> +class. In a normal case the registration will be done by a USB Type-C or PD PHY
> +driver, but it may be a driver for firmware interface such as UCSI, driver for
> +USB PD controller or even driver for Thunderbolt3 controller. This document
> +considers the component registering the USB Type-C ports with the class as "port
> +driver".
> +
> +On top of showing the capabilities, the class also offer the user space control
> +over the roles and alternate modes they support when the port driver is capable
> +of supporting those features.
> +
> +The class provides an API for the port drivers described in this document. The
> +attributes are described in Documentation/ABI/testing/sysfs-class-typec.
> +
> +
> +Interface
> +---------
> +Every port will be presented as its own device under /sys/class/typec/. The
> +first port will be named "usbc0", the second "usbc1" and so on.
> +
> +When connected, the partner will be presented also as its own device under
> +/sys/class/typec/. The parent of the partner device will always be the port. The
> +partner attached to port "usbc0" will be named "usbc0-partner". Full path to the
> +device would be /sys/class/typec/usb0/usb0-partner/.
> +
> +The cable and the two plugs on it may also be optionally presented as their own
> +devices under /sys/class/typec/. The cable attached to the port "usbc0" port
> +will be named usbc0-cable and the plug on the SOP Prime end (see USB Power
> +Delivery Specification ch. 2.4) will be named "usbc-plug0" and on the SOP Double
> +Prime end "usbc0-plug1". The parent of a cable will always be the port, and the
> +parent of the cable plugs will always be the cable.
> +
> +If the port, partner or cable plug support Alternate Modes, every Alternate Mode
> +SVID will have their own device describing them. The Alternate Modes will not be
> +attached to the typec class. For the port's "usbc0" partner, the Alternate Modes
> +would have devices presented under /sys/class/typec/usbc0-partner/. Every mode
> +that is supported will have its own group under the Alternate Mode device named
> +"mode<id>". For example /sys/class/typec/usbc0/usbc0.svid:xxxx/mode0/. The
> +requests for entering/exiting the modes happens with the "active" attribute in
> +that group.
> +
> +
> +API
> +---
> +
> +* Registering the ports
> +
> +The port drivers will describe every Type-C port they control with struct
> +typec_capability data structure, and register them with the following API:
> +
> +struct typec_port *typec_register_port(struct device *dev,
> + const struct typec_capability *cap);
> +
> +The class will provide handle to struct typec_port on success and ERR_PTR on
> +failure. The un-registration of the port happens with the following API:
> +
> +void typec_unregister_port(struct typec_port *port);
> +
> +
> +* Notifications
> +
> +When connection happens on a port, the port driver fills struct typec_connection
> +which is passed to the class. The class provides the following API for reporting
> +connection/disconnection:
> +
> +int typec_connect(struct typec_port *port, struct typec_connection *);
> +void typec_disconnect(struct typec_port *);
> +
> +When the partner end has executed a role change, the port driver uses the
> +following APIs to report it to the class:
> +
> +void typec_set_data_role(struct typec_port *, enum typec_data_role);
> +void typec_set_pwr_role(struct typec_port *, enum typec_role);
> +void typec_set_vconn_role(struct typec_port *, enum typec_role);
> +void typec_set_pwr_opmode(struct typec_port *, enum typec_pwr_opmode);
> +
> +
> +* Alternate Modes
> +
> +After connection, the port drivers register the alternate modes the partner
> +and/or cable plugs support. And before reporting disconnection, the port driver
> +_must_ unregister all the alternate modes registered for the partner and cable
> +plugs. The API takes the struct device of the partner or the cable plug as
> +parameter:
> +
> +int typec_register_altmodes(struct device *, struct typec_altmode *);
> +void typec_unregister_altmodes(struct device *);
> +
> +When the partner end enters or exits the modes, the port driver needs to notify
> +the class with the following API:
> +
> +void typec_altmode_update_active(struct typec_altmode *alt, int mode,
> + bool active);
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 5e25ba1..baa2322 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -12761,6 +12761,15 @@ F: drivers/usb/
> F: include/linux/usb.h
> F: include/linux/usb/
>
> +USB TYPEC SUBSYSTEM
> +M: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +L: linux-usb@vger.kernel.org
> +S: Maintained
> +F: Documentation/ABI/testing/sysfs-class-typec
> +F: Documentation/usb/typec.txt
> +F: drivers/usb/typec/
> +F: include/linux/usb/typec.h
> +
> USB UHCI DRIVER
> M: Alan Stern <stern@rowland.harvard.edu>
> L: linux-usb@vger.kernel.org
> diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
> index fbe493d..89c322e 100644
> --- a/drivers/usb/Kconfig
> +++ b/drivers/usb/Kconfig
> @@ -152,6 +152,8 @@ source "drivers/usb/phy/Kconfig"
>
> source "drivers/usb/gadget/Kconfig"
>
> +source "drivers/usb/typec/Kconfig"
> +
> config USB_LED_TRIG
> bool "USB LED Triggers"
> depends on LEDS_CLASS && LEDS_TRIGGERS
> diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
> index 7791af6..c7f4098 100644
> --- a/drivers/usb/Makefile
> +++ b/drivers/usb/Makefile
> @@ -62,3 +62,5 @@ obj-$(CONFIG_USB_GADGET) += gadget/
> obj-$(CONFIG_USB_COMMON) += common/
>
> obj-$(CONFIG_USBIP_CORE) += usbip/
> +
> +obj-$(CONFIG_TYPEC) += typec/
> diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
> new file mode 100644
> index 0000000..17792f9
> --- /dev/null
> +++ b/drivers/usb/typec/Kconfig
> @@ -0,0 +1,7 @@
> +
> +menu "USB Power Delivery and Type-C drivers"
> +
> +config TYPEC
> + tristate
> +
> +endmenu
> diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
> new file mode 100644
> index 0000000..1012a8b
> --- /dev/null
> +++ b/drivers/usb/typec/Makefile
> @@ -0,0 +1 @@
> +obj-$(CONFIG_TYPEC) += typec.o
> diff --git a/drivers/usb/typec/typec.c b/drivers/usb/typec/typec.c
> new file mode 100644
> index 0000000..d6063e2
> --- /dev/null
> +++ b/drivers/usb/typec/typec.c
> @@ -0,0 +1,1013 @@
> +/*
> + * USB Type-C Connector Class
> + *
> + * Copyright (C) 2016, Intel Corporation
> + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/device.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/usb/typec.h>
> +
> +struct typec_port {
> + unsigned int id;
> + struct device dev;
> +
> + int prefer_role;
> +
> + enum typec_data_role data_role;
> + enum typec_role pwr_role;
> + enum typec_role vconn_role;
> + enum typec_pwr_opmode pwr_opmode;
> +
> + struct typec_partner *partner;
> + struct typec_cable *cable;
> +
> + unsigned int connected:1;
> +
> + const struct typec_capability *cap;
> +};
> +
> +#define to_typec_port(p) container_of(p, struct typec_port, dev)
> +#define to_typec_plug(p) container_of(p, struct typec_plug, dev)
> +#define to_typec_cable(p) container_of(p, struct typec_cable, dev)
> +#define to_typec_partner(p) container_of(p, struct typec_partner, dev)
> +
> +static DEFINE_IDA(typec_index_ida);
> +
> +static struct class typec_class = {
> + .name = "typec",
> +};
> +
> +static const char * const typec_accessory_modes[] = {
> + [TYPEC_ACCESSORY_NONE] = "no",
> + [TYPEC_ACCESSORY_AUDIO] = "Audio Adapter Accessory Mode",
> + [TYPEC_ACCESSORY_DEBUG] = "Debug Accessory Mode",
> +};
> +
> +static struct device_type typec_partner_dev_type;
> +static struct device_type typec_cable_dev_type;
> +static struct device_type typec_port_dev_type;
> +
> +static ssize_t supports_usb_power_delivery_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + if (dev->type == &typec_partner_dev_type) {
> + struct typec_partner *p = to_typec_partner(dev);
> +
> + return sprintf(buf, "%d\n", p->usb_pd);
> + } else if (dev->type == &typec_partner_dev_type) {
> + struct typec_cable *p = to_typec_cable(dev);
> +
> + return sprintf(buf, "%d\n", p->usb_pd);
> + } else {
> + struct typec_port *p = to_typec_port(dev);
> +
> + return sprintf(buf, "%d\n", p->cap->usb_pd);
> + }
> +}
> +static DEVICE_ATTR_RO(supports_usb_power_delivery);
> +
> +/* ------------------------------------------------------------------------- */
> +/* Type-C Partners */
> +
> +static ssize_t accessory_mode_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct typec_partner *p = to_typec_partner(dev);
> +
> + return sprintf(buf, "%s\n", typec_accessory_modes[p->accessory]);
> +}
> +static DEVICE_ATTR_RO(accessory_mode);
> +
> +static struct attribute *typec_partner_attrs[] = {
> + &dev_attr_accessory_mode.attr,
> + &dev_attr_supports_usb_power_delivery.attr,
> + NULL
> +};
> +ATTRIBUTE_GROUPS(typec_partner);
> +
> +static void typec_partner_release(struct device *dev)
> +{
> + struct typec_port *port = to_typec_port(dev->parent);
> +
> + port->partner = NULL;
> +}
> +
> +static struct device_type typec_partner_dev_type = {
> + .name = "typec_partner_device",
> + .groups = typec_partner_groups,
> + .release = typec_partner_release,
> +};
> +
> +static int
> +typec_add_partner(struct typec_port *port, struct typec_partner *partner)
> +{
> + struct device *dev = &partner->dev;
> + int ret;
> +
> + dev->class = &typec_class;
> + dev->parent = &port->dev;
> + dev->type = &typec_partner_dev_type;
> + dev_set_name(dev, "%s-partner", dev_name(&port->dev));
> +
> + ret = device_register(dev);
> + if (ret) {
> + put_device(dev);
> + return ret;
> + }
> +
> + port->partner = partner;
> + return 0;
> +}
> +
> +static void typec_remove_partner(struct typec_port *port)
> +{
> + device_unregister(&port->partner->dev);
> +}
> +
> +/* ------------------------------------------------------------------------- */
> +/* Type-C Cable Plugs */
> +
> +static void typec_plug_release(struct device *dev)
> +{
> + struct typec_plug *plug = to_typec_plug(dev);
> +
> + memset(plug, 0, sizeof(*plug));
> +}
> +
> +static struct device_type typec_plug_dev_type = {
> + .name = "typec_plug_device",
> + .release = typec_plug_release,
> +};
> +
> +static int
> +typec_add_plug(struct typec_port *port, struct typec_plug *plug)
> +{
> + struct device *dev = &plug->dev;
> + char name[8];
> + int ret;
> +
> + sprintf(name, "plug%d", plug->index);
> +
> + dev->class = &typec_class;
> + dev->parent = &port->cable->dev;
> + dev->type = &typec_plug_dev_type;
> + dev_set_name(dev, "%s-%s", dev_name(&port->dev), name);
> +
> + ret = device_register(dev);
> + if (ret) {
> + put_device(dev);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void typec_remove_plug(struct typec_plug *plug)
> +{
> + device_unregister(&plug->dev);
> +}
> +
> +/* Type-C Cables */
> +
> +static ssize_t
> +active_show(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> + struct typec_cable *cable = to_typec_cable(dev);
> +
> + return sprintf(buf, "%d\n", cable->active);
> +}
> +static DEVICE_ATTR_RO(active);
> +
> +static const char * const typec_plug_types[] = {
> + [USB_PLUG_NONE] = "unknown",
> + [USB_PLUG_TYPE_A] = "Type-A",
> + [USB_PLUG_TYPE_B] = "Type-B",
> + [USB_PLUG_TYPE_C] = "Type-C",
> + [USB_PLUG_CAPTIVE] = "Captive",
> +};
> +
> +static ssize_t plug_type_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct typec_cable *cable = to_typec_cable(dev);
> +
> + return sprintf(buf, "%s\n", typec_plug_types[cable->type]);
> +}
> +static DEVICE_ATTR_RO(plug_type);
> +
> +static struct attribute *typec_cable_attrs[] = {
> + &dev_attr_active.attr,
> + &dev_attr_plug_type.attr,
> + &dev_attr_supports_usb_power_delivery.attr,
> + NULL
> +};
> +ATTRIBUTE_GROUPS(typec_cable);
> +
> +static void typec_cable_release(struct device *dev)
> +{
> + struct typec_port *port = to_typec_port(dev->parent);
> +
> + port->cable = NULL;
> +}
> +
> +static struct device_type typec_cable_dev_type = {
> + .name = "typec_cable_device",
> + .groups = typec_cable_groups,
> + .release = typec_cable_release,
> +};
> +
> +static int typec_add_cable(struct typec_port *port, struct typec_cable *cable)
> +{
> + struct device *dev = &cable->dev;
> + int ret;
> +
> + dev->class = &typec_class;
> + dev->parent = &port->dev;
> + dev->type = &typec_cable_dev_type;
> + dev_set_name(dev, "%s-cable", dev_name(&port->dev));
> +
> + ret = device_register(dev);
> + if (ret) {
> + put_device(dev);
> + return ret;
> + }
> +
> + /* Plug1 */
> + if (!cable->usb_pd)
> + return 0;
> +
> + cable->plug[0].index = 1;
> + ret = typec_add_plug(port, &cable->plug[0]);
> + if (ret) {
> + device_unregister(dev);
> + return ret;
> + }
> +
> + /* Plug2 */
> + if (!cable->active || !cable->sop_pp_controller)
> + return 0;
> +
> + cable->plug[1].index = 2;
> + ret = typec_add_plug(port, &cable->plug[1]);
> + if (ret) {
> + typec_remove_plug(&cable->plug[0]);
> + device_unregister(dev);
> + return ret;
> + }
> +
> + port->cable = cable;
> + return 0;
> +}
> +
> +static void typec_remove_cable(struct typec_port *port)
> +{
> + if (port->cable->active) {
> + typec_remove_plug(&port->cable->plug[0]);
> + if (port->cable->sop_pp_controller)
> + typec_remove_plug(&port->cable->plug[1]);
> + }
> + device_unregister(&port->cable->dev);
> +}
> +
> +/* ------------------------------------------------------------------------- */
> +/* API for the port drivers */
> +
> +static void typec_init_roles(struct typec_port *port)
> +{
> + if (port->prefer_role < 0)
> + return;
> +
> + if (port->prefer_role == TYPEC_SOURCE) {
> + port->data_role = TYPEC_HOST;
> + port->pwr_role = TYPEC_SOURCE;
> + port->vconn_role = TYPEC_SOURCE;
> + } else {
> + /* Device mode as default also by default with DRP ports */
> + port->data_role = TYPEC_DEVICE;
> + port->pwr_role = TYPEC_SINK;
> + port->vconn_role = TYPEC_SINK;
> + }
> +}
> +
> +int typec_connect(struct typec_port *port, struct typec_connection *con)
> +{
> + int ret;
> +
> + if (!con->partner && !con->cable)
> + return -EINVAL;
> +
> + port->connected = 1;
> + port->data_role = con->data_role;
> + port->pwr_role = con->pwr_role;
> + port->vconn_role = con->vconn_role;
> + port->pwr_opmode = con->pwr_opmode;
> +
> + if (con->cable) {
> + ret = typec_add_cable(port, con->cable);
> + if (ret)
> + return ret;
> + }
> +
> + if (con->partner) {
> + ret = typec_add_partner(port, con->partner);
> + if (ret) {
> + if (con->cable)
> + typec_remove_cable(port);
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(typec_connect);
> +
> +void typec_disconnect(struct typec_port *port)
> +{
> + if (port->partner)
> + typec_remove_partner(port);
> +
> + if (port->cable)
> + typec_remove_cable(port);
> +
> + port->connected = 0;
> +
> + port->pwr_opmode = TYPEC_PWR_MODE_USB;
> +
> + typec_init_roles(port);
> +}
> +EXPORT_SYMBOL_GPL(typec_disconnect);
> +
> +/* --------------------------------------- */
> +/* Driver callbacks to report role updates */
> +
> +void typec_set_data_role(struct typec_port *port, enum typec_data_role role)
> +{
> + port->data_role = role;
> + sysfs_notify(&port->dev.kobj, NULL, "current_data_role");
> + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
> +}
> +EXPORT_SYMBOL_GPL(typec_set_data_role);
> +
> +void typec_set_pwr_role(struct typec_port *port, enum typec_role role)
> +{
> + port->pwr_role = role;
> + sysfs_notify(&port->dev.kobj, NULL, "current_power_role");
> + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
> +}
> +EXPORT_SYMBOL_GPL(typec_set_pwr_role);
> +
> +void typec_set_vconn_role(struct typec_port *port, enum typec_role role)
> +{
> + port->vconn_role = role;
> + sysfs_notify(&port->dev.kobj, NULL, "current_vconn_role");
> + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
> +}
> +EXPORT_SYMBOL_GPL(typec_set_vconn_role);
> +
> +void typec_set_pwr_opmode(struct typec_port *port,
> + enum typec_pwr_opmode opmode)
> +{
> + port->pwr_opmode = opmode;
> + sysfs_notify(&port->dev.kobj, NULL, "power_operation_mode");
> + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
> +}
> +EXPORT_SYMBOL_GPL(typec_set_pwr_opmode);
> +
> +/* -------------------------------- */
> +/* Alternate Modes */
> +
> +/*
> + * typec_altmode_update_active - Notify about Enter/Exit mode
> + * @alt: Handle to the Alternate Mode
> + * @mode: Mode id
> + * @active: True when the mode has been enterred
> + */
> +void typec_altmode_update_active(struct typec_altmode *alt, int mode,
> + bool active)
> +{
> + struct typec_mode *m = alt->modes + mode;
> + char dir[6];
> +
> + m->active = active;
> + snprintf(dir, 6, "mode%d", mode);
> + sysfs_notify(&alt->dev.kobj, dir, "active");
> + kobject_uevent(&alt->dev.kobj, KOBJ_CHANGE);
> +}
> +EXPORT_SYMBOL_GPL(typec_altmode_update_active);
> +
> +/*
> + * typec_altmode2port - Alternate Mode to USB Type-C port
> + * @alt: The Alternate Mode
> + *
> + * Returns the port that the cable plug or partner with @alt is connected to.
> + */
> +struct typec_port *typec_altmode2port(struct typec_altmode *alt)
> +{
> + if (alt->dev.parent->type == &typec_plug_dev_type)
> + return to_typec_port(alt->dev.parent->parent->parent);
> + if (alt->dev.parent->type == &typec_partner_dev_type)
> + return to_typec_port(alt->dev.parent->parent);
> + if (alt->dev.parent->type == &typec_port_dev_type)
> + return to_typec_port(alt->dev.parent);
> +
> + return NULL;
> +}
> +EXPORT_SYMBOL_GPL(typec_altmode2port);
> +
> +static void typec_altmode_release(struct device *dev)
> +{
> + struct typec_altmode *alt = to_altmode(dev);
> +
> + kfree(alt->mode_groups);
> +}
> +
> +static ssize_t
> +typec_altmode_vdo_show(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + struct typec_mode *mode = container_of(attr, struct typec_mode,
> + vdo_attr);
> +
> + return sprintf(buf, "0x%08x\n", mode->vdo);
> +}
> +
> +static ssize_t
> +typec_altmode_desc_show(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + struct typec_mode *mode = container_of(attr, struct typec_mode,
> + desc_attr);
> +
> + return sprintf(buf, "%s\n", mode->desc ? mode->desc : "");
> +}
> +
> +static ssize_t
> +typec_altmode_active_show(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + struct typec_mode *mode = container_of(attr, struct typec_mode,
> + active_attr);
> +
> + return sprintf(buf, "%d\n", mode->active);
> +}
> +
> +static ssize_t
> +typec_altmode_active_store(struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t size)
> +{
> + struct typec_mode *mode = container_of(attr, struct typec_mode,
> + active_attr);
> + struct typec_port *port = typec_altmode2port(mode->alt_mode);
> + bool activate;
> + int ret;
> +
> + if (!port->cap->activate_mode)
> + return -EOPNOTSUPP;
> +
> + ret = kstrtobool(buf, &activate);
> + if (ret)
> + return ret;
> +
> + ret = port->cap->activate_mode(mode->alt_mode, mode->index, activate);
> + if (ret)
> + return ret;
> +
> + return size;
> +}
> +
> +static ssize_t
> +typec_altmode_roles_show(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + struct typec_mode *mode = container_of(attr, struct typec_mode,
> + roles_attr);
> + ssize_t ret;
> +
> + switch (mode->roles) {
> + case TYPEC_PORT_DFP:
> + ret = sprintf(buf, "source\n");
> + break;
> + case TYPEC_PORT_UFP:
> + ret = sprintf(buf, "sink\n");
> + break;
> + case TYPEC_PORT_DRP:
> + default:
> + ret = sprintf(buf, "source, sink\n");
> + break;
> + }
> + return ret;
> +}
> +
> +static void typec_init_modes(struct typec_altmode *alt, int is_port)
> +{
> + struct typec_mode *mode = alt->modes;
> + int i;
> +
> + for (i = 0; i < alt->n_modes; i++, mode++) {
> + mode->alt_mode = alt;
> + mode->index = i;
> + sprintf(mode->group_name, "mode%d", i);
> +
> + sysfs_attr_init(&mode->vdo_attr.attr);
> + mode->vdo_attr.attr.name = "vdo";
> + mode->vdo_attr.attr.mode = 0444;
> + mode->vdo_attr.show = typec_altmode_vdo_show;
> +
> + sysfs_attr_init(&mode->desc_attr.attr);
> + mode->desc_attr.attr.name = "description";
> + mode->desc_attr.attr.mode = 0444;
> + mode->desc_attr.show = typec_altmode_desc_show;
> +
> + sysfs_attr_init(&mode->active_attr.attr);
> + mode->active_attr.attr.name = "active";
> + mode->active_attr.attr.mode = 0644;
> + mode->active_attr.show = typec_altmode_active_show;
> + mode->active_attr.store = typec_altmode_active_store;
> +
> + mode->attrs[0] = &mode->vdo_attr.attr;
> + mode->attrs[1] = &mode->desc_attr.attr;
> + mode->attrs[2] = &mode->active_attr.attr;
> +
> + /* With ports, list the roles that the mode is supported with */
> + if (is_port) {
> + sysfs_attr_init(&mode->roles_attr.attr);
> + mode->roles_attr.attr.name = "supported_roles";
> + mode->roles_attr.attr.mode = 0444;
> + mode->roles_attr.show = typec_altmode_roles_show;
> +
> + mode->attrs[3] = &mode->roles_attr.attr;
> + }
> +
> + mode->group.attrs = mode->attrs;
> + mode->group.name = mode->group_name;
> +
> + alt->mode_groups[i] = &mode->group;
> + }
> +}
> +
> +static int
> +typec_add_altmode(struct device *parent, struct typec_altmode *alt, int is_port)
> +{
> + struct device *dev = &alt->dev;
> + int ret;
> +
> + alt->mode_groups = kcalloc(alt->n_modes + 1,
> + sizeof(struct attribute_group *),
> + GFP_KERNEL);
> + if (!alt->mode_groups)
> + return -ENOMEM;
> +
> + typec_init_modes(alt, is_port);
> +
> + dev->groups = alt->mode_groups;
> + dev->release = typec_altmode_release;
> + dev->parent = parent;
> + /* TODO: if (!is_port) dev->bus = &typec_altmode_bus; */
> +
> + dev_set_name(dev, "%s.svid:%04x", dev_name(parent), alt->svid);
> +
> + ret = device_register(dev);
> + if (ret) {
> + put_device(dev);
> + kfree(alt->mode_groups);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int __typec_register_altmodes(struct device *parent,
> + struct typec_altmode *alt_modes,
> + int is_port)
> +{
> + struct typec_altmode *alt;
> + int index;
> + int ret;
> +
> + if (!alt_modes)
> + return 0;
> +
> + for (alt = alt_modes, index = 0; alt->svid; alt++, index++) {
> + ret = typec_add_altmode(parent, alt, is_port);
> + if (ret)
> + goto err;
> + }
> +
> + return 0;
> +err:
> + for (alt = alt_modes + index; index; alt--, index--)
> + device_unregister(&alt->dev);
> +
> + return ret;
> +}
> +
> +int typec_register_altmodes(struct device *dev, struct typec_altmode *alt_modes)
> +{
> + if (dev->type == &typec_partner_dev_type) {
> + struct typec_partner *p = to_typec_partner(dev);
> +
> + p->alt_modes = alt_modes;
> + } else if (dev->type == &typec_plug_dev_type) {
> + struct typec_plug *p = to_typec_plug(dev);
> +
> + p->alt_modes = alt_modes;
> + } else {
> + return -ENODEV;
> + }
> +
> + return __typec_register_altmodes(dev, alt_modes, false);
> +}
> +EXPORT_SYMBOL_GPL(typec_register_altmodes);
> +
> +void typec_unregister_altmodes(struct device *dev)
> +{
> + struct typec_altmode *alt_modes = NULL;
> + struct typec_altmode *alt;
> +
> + if (dev->type == &typec_partner_dev_type) {
> + struct typec_partner *p = to_typec_partner(dev);
> +
> + alt_modes = p->alt_modes;
> + p->alt_modes = NULL;
> + } else if (dev->type == &typec_plug_dev_type) {
> + struct typec_plug *p = to_typec_plug(dev);
> +
> + alt_modes = p->alt_modes;
> + p->alt_modes = NULL;
> + }
> +
> + if (!alt_modes)
> + return;
> +
> + for (alt = alt_modes; alt->svid; alt++)
> + device_unregister(&alt->dev);
> +}
> +EXPORT_SYMBOL_GPL(typec_unregister_altmodes);
> +
> +/* ------------------------------------------------------------------------- */
> +/* USB Type-C ports */
> +
> +static const char * const typec_roles[] = {
> + [TYPEC_SINK] = "sink",
> + [TYPEC_SOURCE] = "source",
> +};
> +
> +static const char * const typec_data_roles[] = {
> + [TYPEC_DEVICE] = "device",
> + [TYPEC_HOST] = "host",
> +};
> +
> +static ssize_t
> +preferred_role_store(struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t size)
> +{
> + struct typec_port *port = to_typec_port(dev);
> + int role;
> + int ret;
> +
> + if (port->cap->type != TYPEC_PORT_DRP) {
> + dev_dbg(dev, "Preferred role only supported with DRP ports\n");
> + return -EOPNOTSUPP;
> + }
> +
> + if (!port->cap->try_role) {
> + dev_dbg(dev, "Setting preferred role not supported\n");
> + return -EOPNOTSUPP;
> + }
> +
> + role = sysfs_match_string(typec_roles, buf);
> + if (role < 0) {
> + if (sysfs_streq(buf, "none"))
> + role = TYPEC_NO_PREFERRED_ROLE;
> + else
> + return -EINVAL;
> + }
> +
> + ret = port->cap->try_role(port->cap, role);
> + if (ret)
> + return ret;
> +
> + port->prefer_role = role;
> + return size;
> +}
> +
> +static ssize_t
> +preferred_role_show(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + struct typec_port *port = to_typec_port(dev);
> +
> + if (port->prefer_role < 0)
> + return 0;
> +
> + return sprintf(buf, "%s\n", typec_roles[port->prefer_role]);
> +}
> +static DEVICE_ATTR_RW(preferred_role);
> +
> +static ssize_t
> +current_data_role_store(struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t size)
> +{
> + struct typec_port *port = to_typec_port(dev);
> + int ret;
> +
> + if (port->cap->type != TYPEC_PORT_DRP) {
> + dev_dbg(dev, "data role swap only supported with DRP ports\n");
> + return -EOPNOTSUPP;
> + }
> +
> + if (!port->cap->dr_set) {
> + dev_dbg(dev, "data role swapping not supported\n");
> + return -EOPNOTSUPP;
> + }
> +
> + ret = sysfs_match_string(typec_data_roles, buf);
> + if (ret < 0)
> + return ret;
> +
> + ret = port->cap->dr_set(port->cap, ret);
> + if (ret)
> + return ret;
> +
> + return size;
> +}
> +
> +static ssize_t
> +current_data_role_show(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + struct typec_port *port = to_typec_port(dev);
> +
> + return sprintf(buf, "%s\n", typec_data_roles[port->data_role]);
> +}
> +static DEVICE_ATTR_RW(current_data_role);
> +
> +static ssize_t supported_data_roles_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct typec_port *port = to_typec_port(dev);
> +
> + if (port->cap->type == TYPEC_PORT_DRP)
> + return sprintf(buf, "host, device\n");
> +
> + return sprintf(buf, "%s\n", typec_data_roles[port->data_role]);
> +}
> +static DEVICE_ATTR_RO(supported_data_roles);
> +
> +static ssize_t current_power_role_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t size)
> +{
> + struct typec_port *port = to_typec_port(dev);
> + int ret = size;
> +
> + if (!port->cap->usb_pd) {
> + dev_dbg(dev, "power role swap only supported with USB PD\n");
> + return -EOPNOTSUPP;
> + }
> +
> + if (!port->cap->pr_set) {
> + dev_dbg(dev, "power role swapping not supported\n");
> + return -EOPNOTSUPP;
> + }
> +
> + if (port->pwr_opmode != TYPEC_PWR_MODE_PD) {
> + dev_dbg(dev, "partner unable to swap power role\n");
> + return -EIO;
> + }
> +
> + ret = sysfs_match_string(typec_roles, buf);
> + if (ret < 0)
> + return ret;
> +
> + ret = port->cap->pr_set(port->cap, ret);
> + if (ret)
> + return ret;
> +
> + return size;
> +}
> +
> +static ssize_t current_power_role_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct typec_port *port = to_typec_port(dev);
> +
> + return sprintf(buf, "%s\n", typec_roles[port->pwr_role]);
> +}
> +static DEVICE_ATTR_RW(current_power_role);
> +
> +static ssize_t supported_power_roles_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct typec_port *port = to_typec_port(dev);
> +
> + if (port->cap->usb_pd || port->cap->type == TYPEC_PORT_DRP)
> + return sprintf(buf, "source, sink\n");
> +
> + return sprintf(buf, "%s\n", typec_roles[port->pwr_role]);
> +}
> +static DEVICE_ATTR_RO(supported_power_roles);
> +
> +static const char * const typec_pwr_opmodes[] = {
> + [TYPEC_PWR_MODE_USB] = "USB",
> + [TYPEC_PWR_MODE_1_5A] = "USB Type-C 1.5A",
> + [TYPEC_PWR_MODE_3_0A] = "USB Type-C 3.0A",
> + [TYPEC_PWR_MODE_PD] = "USB Power Delivery",
> +};
> +
> +static ssize_t power_operation_mode_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct typec_port *port = to_typec_port(dev);
> +
> + return sprintf(buf, "%s\n", typec_pwr_opmodes[port->pwr_opmode]);
> +}
> +static DEVICE_ATTR_RO(power_operation_mode);
> +
> +static ssize_t vconn_source_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t size)
> +{
> + struct typec_port *port = to_typec_port(dev);
> + enum typec_role role;
> + int ret;
> +
> + if (!port->cap->usb_pd) {
> + dev_dbg(dev, "vconn swap only supported with USB PD\n");
> + return -EOPNOTSUPP;
> + }
> +
> + if (!port->cap->vconn_set) {
> + dev_dbg(dev, "vconn swapping not supported\n");
> + return -EOPNOTSUPP;
> + }
> +
> + if (sysfs_streq(buf, "1"))
> + role = TYPEC_SOURCE;
> + else if (sysfs_streq(buf, "0"))
> + role = TYPEC_SINK;
> + else
> + return -EINVAL;
> +
> + ret = port->cap->vconn_set(port->cap, role);
> + if (ret)
> + return ret;
> +
> + return size;
> +}
> +
> +static ssize_t vconn_source_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct typec_port *port = to_typec_port(dev);
> +
> + return sprintf(buf, "%d\n", port->vconn_role == TYPEC_SOURCE ? 1 : 0);
> +}
> +static DEVICE_ATTR_RW(vconn_source);
> +
> +static ssize_t supported_accessory_modes_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct typec_port *port = to_typec_port(dev);
> + ssize_t ret = 0;
> + int i;
> +
> + if (!port->cap->accessory)
> + return 0;
> +
> + for (i = 0; port->cap->accessory[i]; i++)
> + ret += sprintf(buf + ret, "%s\n",
> + typec_accessory_modes[port->cap->accessory[i]]);
> + return ret;
> +}
> +static DEVICE_ATTR_RO(supported_accessory_modes);
> +
> +static struct attribute *typec_attrs[] = {
> + &dev_attr_current_power_role.attr,
> + &dev_attr_current_data_role.attr,
> + &dev_attr_power_operation_mode.attr,
> + &dev_attr_preferred_role.attr,
> + &dev_attr_supported_accessory_modes.attr,
> + &dev_attr_supported_data_roles.attr,
> + &dev_attr_supported_power_roles.attr,
> + &dev_attr_supports_usb_power_delivery.attr,
> + &dev_attr_vconn_source.attr,
> + NULL,
> +};
> +ATTRIBUTE_GROUPS(typec);
> +
> +static int typec_uevent(struct device *dev, struct kobj_uevent_env *env)
> +{
> + int ret;
> +
> + ret = add_uevent_var(env, "TYPEC_PORT=%s", dev_name(dev));
> + if (ret)
> + dev_err(dev, "failed to add uevent TYPEC_PORT\n");
> +
> + return ret;
> +}
> +
> +static void typec_release(struct device *dev)
> +{
> + struct typec_port *port = to_typec_port(dev);
> +
> + ida_simple_remove(&typec_index_ida, port->id);
> + kfree(port);
> +}
> +
> +static struct device_type typec_port_dev_type = {
> + .name = "typec_port",
> + .groups = typec_groups,
> + .uevent = typec_uevent,
> + .release = typec_release,
> +};
> +
> +struct typec_port *typec_register_port(struct device *dev,
> + const struct typec_capability *cap)
> +{
> + struct typec_port *port;
> + int ret;
> + int id;
> +
> + port = kzalloc(sizeof(*port), GFP_KERNEL);
> + if (!port)
> + return ERR_PTR(-ENOMEM);
> +
> + id = ida_simple_get(&typec_index_ida, 0, 0, GFP_KERNEL);
> + if (id < 0) {
> + kfree(port);
> + return ERR_PTR(id);
> + }
> +
> + port->prefer_role = TYPEC_NO_PREFERRED_ROLE;
> +
> + port->id = id;
> + port->cap = cap;
> + port->dev.type = &typec_port_dev_type;
> + port->dev.class = &typec_class;
> + port->dev.parent = dev;
> + dev_set_name(&port->dev, "usbc%d", id);
> +
> + typec_init_roles(port);
> +
> + ret = device_register(&port->dev);
> + if (ret)
> + goto reg_err;
> +
> + ret = __typec_register_altmodes(&port->dev, cap->alt_modes, true);
> + if (ret)
> + goto alt_err;
> +
> + return port;
> +alt_err:
> + device_unregister(&port->dev);
> +reg_err:
> + ida_simple_remove(&typec_index_ida, id);
> + put_device(&port->dev);
> + kfree(port);
> + return ERR_PTR(ret);
> +}
> +EXPORT_SYMBOL_GPL(typec_register_port);
> +
> +void typec_unregister_port(struct typec_port *port)
> +{
> + struct typec_altmode *alt;
> +
> + WARN_ON(port->connected);
> +
> + if (port->cap->alt_modes)
> + for (alt = port->cap->alt_modes; alt->svid; alt++)
> + device_unregister(&alt->dev);
> + device_unregister(&port->dev);
> +}
> +EXPORT_SYMBOL_GPL(typec_unregister_port);
> +
> +static int __init typec_init(void)
> +{
> + return class_register(&typec_class);
> +}
> +subsys_initcall(typec_init);
> +
> +static void __exit typec_exit(void)
> +{
> + ida_destroy(&typec_index_ida);
> + class_unregister(&typec_class);
> +}
> +module_exit(typec_exit);
> +
> +MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>");
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("USB Type-C Connector Class");
> diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
> new file mode 100644
> index 0000000..31ea45b
> --- /dev/null
> +++ b/include/linux/usb/typec.h
> @@ -0,0 +1,252 @@
> +
> +#ifndef __LINUX_USB_TYPEC_H
> +#define __LINUX_USB_TYPEC_H
> +
> +#include <linux/device.h>
> +#include <linux/types.h>
> +
> +struct typec_port;
> +
> +enum typec_port_type {
> + TYPEC_PORT_DFP,
> + TYPEC_PORT_UFP,
> + TYPEC_PORT_DRP,
> +};
> +
> +enum typec_plug_type {
> + USB_PLUG_NONE,
> + USB_PLUG_TYPE_A,
> + USB_PLUG_TYPE_B,
> + USB_PLUG_TYPE_C,
> + USB_PLUG_CAPTIVE,
> +};
> +
> +enum typec_data_role {
> + TYPEC_DEVICE,
> + TYPEC_HOST,
> +};
> +
> +enum typec_role {
> + TYPEC_SINK,
> + TYPEC_SOURCE,
> +};
> +
> +enum typec_pwr_opmode {
> + TYPEC_PWR_MODE_USB,
> + TYPEC_PWR_MODE_1_5A,
> + TYPEC_PWR_MODE_3_0A,
> + TYPEC_PWR_MODE_PD,
> +};
> +
> +enum typec_accessory {
> + TYPEC_ACCESSORY_NONE,
> + TYPEC_ACCESSORY_AUDIO,
> + TYPEC_ACCESSORY_DEBUG,
> +};
> +
> +/*
> + * struct typec_mode - Individual Mode of an Alternate Mode
> + * @vdo: VDO returned by Discover Modes USB PD command
> + * @desc: Mode description
> + * @active: Tells if the mode is currently entered or not
> + * @index: Index of the mode
> + * @group_name: Name for the sysfs folder in form "mode<index>"
> + * @group: The sysfs group (folder) for the mode
> + * @attrs: The attributes for the sysfs group
> + * @vdo_attr: Device attribute to expose the VDO of the mode
> + * @desc_attr: Device attribute to expose the description of the mode
> + * @active_attr: Device attribute to expose active of the mode
> + * @roles: Only for ports. DRP if the mode is awailable in both roles
> + * @roles_attr: Device attribute, only for ports, to expose the supported roles
> + *
> + * Details about a mode of an Alternate Mode which a connector, cable plug or
> + * partner supports. Every mode will have it's own sysfs group. The details are
> + * the VDO returned by discover modes command, description for the mode and
> + * active flag telling is the mode currently active or not.
> + */
> +struct typec_mode {
> + u32 vdo;
> + char *desc;
> + unsigned int active:1;
> + /* Only for ports */
> + enum typec_port_type roles;
> +
> + struct typec_altmode *alt_mode;
> +
> + int index;
> + char group_name[8];
> + struct attribute_group group;
> + struct attribute *attrs[5];
> + struct device_attribute vdo_attr;
> + struct device_attribute desc_attr;
> + struct device_attribute active_attr;
> + /* Only for ports */
> + struct device_attribute roles_attr;
> +};
> +
> +/*
> + * struct typec_altmode - USB Type-C Alternate Mode
> + * @dev: struct device instance
> + * @name: Name for the Alternate Mode (optional)
> + * @svid: Standard or Vendor ID
> + * @n_modes: Number of modes
> + * @modes: Array of modes supported by the Alternat Mode
> + * @mode_groups: The modes as attribute groups to be exposed in sysfs
> + *
> + * Representation of an Alternate Mode that has SVID assigned by USB-IF. The
> + * array of modes will list the modes of a particular SVID that are supported by
> + * a connector, partner of a cable plug.
> + */
> +struct typec_altmode {
> + struct device dev;
> + char *name;
> +
> + u16 svid;
> + int n_modes;
> + struct typec_mode *modes;
> +
> + const struct attribute_group **mode_groups;
> +};
> +
> +#define to_altmode(d) container_of(d, struct typec_altmode, dev)
> +
> +struct typec_port *typec_altmode2port(struct typec_altmode *alt);
> +
> +void typec_altmode_update_active(struct typec_altmode *alt, int mode,
> + bool active);
> +
> +int typec_register_altmodes(struct device *dev, struct typec_altmode *alt);
> +void typec_unregister_altmodes(struct device *dev);
> +
> +/*
> + * struct typec_plug - USB Type-C Cable Plug
> + * @dev: struct device instance
> + * @index: 1 for the plug connected to DFP and 2 for the plug connected to UFP
> + * @alt_modes: Alternate Modes the cable plug supports (null terminated)
> + *
> + * Represents USB Type-C Cable Plug.
> + */
> +struct typec_plug {
> + struct device dev;
> + int index;
> + struct typec_altmode *alt_modes;
> +};
> +
> +/*
> + * struct typec_cable - USB Type-C Cable
> + * @dev: struct device instance
> + * @type: The plug type from USB PD Cable VDO
> + * @usb_pd: Electronically Marked Cable
> + * @active: Is the cable active or passive
> + * @sop_pp_controller: Tells whether both cable plugs are configurable or not
> + * @plug: The two plugs in the cable.
> + *
> + * Represents USB Type-C Cable attached to USB Type-C port. Two plugs are
> + * created if the cable has SOP Double Prime controller as defined in USB PD
> + * specification. Otherwise only one will be created if the cable is active. For
> + * passive cables no plugs are created.
> + */
> +struct typec_cable {
> + struct device dev;
> + enum typec_plug_type type;
> + u32 vdo;
> + unsigned int usb_pd:1;
> + unsigned int active:1;
> + unsigned int sop_pp_controller:1;
> +
> + struct typec_plug plug[2];
> +};
> +
> +/*
> + * struct typec_partner - USB Type-C Partner
> + * @dev: struct device instance
> + * @type: Normal USB device, charger, Alternate Mode or Accessory
> + * @usb_pd: USB Power Delivery support
> + * @vdo: VDO returned by Discover Identity USB PD command
> + * @alt_modes: Alternate Modes the partner supports (null terminated)
> + *
> + * Details about a partner that is attached to USB Type-C port.
> + */
> +struct typec_partner {
> + struct device dev;
> + unsigned int usb_pd:1;
> + u32 vdo;
> + enum typec_accessory accessory;
> + struct typec_altmode *alt_modes;
> +};
> +
> +/*
> + * struct typec_capability - USB Type-C Port Capabilities
> + * @role: DFP (Host-only), UFP (Device-only) or DRP (Dual Role)
> + * @usb_pd: USB Power Delivery support
> + * @accessory: Supported Accessory Modes (NULL terminated array)
> + * @alt_modes: Alternate Modes the connector supports (NULL terminated)
> + * @try_role: Set data role preference for DRP port
> + * @dr_set: Set Data Role
> + * @pr_set: Set Power Role
> + * @vconn_set: Set VCONN Role
> + * @activate_mode: Enter/exit given Alternate Mode
> + *
> + * Static capabilities of a single USB Type-C port.
> + */
> +struct typec_capability {
> + enum typec_port_type type;
> + unsigned int usb_pd:1;
> + enum typec_accessory *accessory;
> + struct typec_altmode *alt_modes;
> +
> + int (*try_role)(const struct typec_capability *,
> + int role);
> +
> + int (*dr_set)(const struct typec_capability *,
> + enum typec_data_role);
> + int (*pr_set)(const struct typec_capability *,
> + enum typec_role);
> + int (*vconn_set)(const struct typec_capability *,
> + enum typec_role);
> +
> + int (*activate_mode)(struct typec_altmode *,
> + int mode, int activate);
> +};
> +
> +/* Specific to try_role(). Indicates the user want's to clear the preference. */
> +#define TYPEC_NO_PREFERRED_ROLE (-1)
> +
> +/*
> + * struct typec_connection - Details about USB Type-C port connection event
> + * @partner: The attached partner
> + * @cable: The attached cable
> + * @data_role: Initial USB data role (host or device)
> + * @pwr_role: Initial Power role (source or sink)
> + * @vconn_role: Initial VCONN role (source or sink)
> + * @pwr_opmode: The power mode of the connection
> + *
> + * All the relevant details about a connection event. Wrapper that is passed to
> + * typec_connect(). The context is copied when typec_connect() is called and the
> + * structure is not used for anything else.
> + */
> +struct typec_connection {
> + struct typec_partner *partner;
> + struct typec_cable *cable;
> +
> + enum typec_data_role data_role;
> + enum typec_role pwr_role;
> + enum typec_role vconn_role;
> + enum typec_pwr_opmode pwr_opmode;
> +};
> +
> +struct typec_port *typec_register_port(struct device *dev,
> + const struct typec_capability *cap);
> +void typec_unregister_port(struct typec_port *port);
> +
> +int typec_connect(struct typec_port *port, struct typec_connection *con);
> +void typec_disconnect(struct typec_port *port);
> +
> +/* Callbacks from driver */
> +
> +void typec_set_data_role(struct typec_port *port, enum typec_data_role role);
> +void typec_set_pwr_role(struct typec_port *port, enum typec_role role);
> +void typec_set_vconn_role(struct typec_port *port, enum typec_role role);
> +void typec_set_pwr_opmode(struct typec_port *port, enum typec_pwr_opmode mode);
> +
> +#endif /* __LINUX_USB_TYPEC_H */
> --
> 2.10.2
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCHv12 2/3] usb: USB Type-C connector class
2016-11-22 14:11 ` [PATCHv12 2/3] usb: USB Type-C connector class Heikki Krogerus
2016-11-23 16:27 ` Guenter Roeck
2016-11-24 0:17 ` Guenter Roeck
@ 2016-11-24 5:12 ` Guenter Roeck
2016-11-24 9:57 ` Heikki Krogerus
2 siblings, 1 reply; 20+ messages in thread
From: Guenter Roeck @ 2016-11-24 5:12 UTC (permalink / raw)
To: Heikki Krogerus, Greg KH
Cc: Badhri Jagan Sridharan, Oliver Neukum, linux-kernel, linux-usb
Hello Heikki,
On 11/22/2016 06:11 AM, Heikki Krogerus wrote:
[ ... ]
> +
> +struct typec_port *typec_register_port(struct device *dev,
> + const struct typec_capability *cap)
> +{
> + struct typec_port *port;
> + int ret;
> + int id;
> +
> + port = kzalloc(sizeof(*port), GFP_KERNEL);
> + if (!port)
> + return ERR_PTR(-ENOMEM);
> +
> + id = ida_simple_get(&typec_index_ida, 0, 0, GFP_KERNEL);
> + if (id < 0) {
> + kfree(port);
> + return ERR_PTR(id);
> + }
> +
> + port->prefer_role = TYPEC_NO_PREFERRED_ROLE;
> +
Following up on this:
In our implementation, the default preferred role is determined by the
low level driver (as, in my understanding, is suggested by the standard).
This means that the ABI will report "no preferred role", unless user space
overwrites it, even though there _is_ in fact a preferred role, and the
low level driver will execute try.src or try.snk based on that role.
It might make sense to add the preferred role to struct typec_capability
and get the initial value from there. If a low level driver does not want
to specify it, it can easily set its value to TYPEC_NO_PREFERRED_ROLE.
Thanks,
Guenter
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCHv12 2/3] usb: USB Type-C connector class
2016-11-24 5:12 ` Guenter Roeck
@ 2016-11-24 9:57 ` Heikki Krogerus
2016-11-28 10:19 ` Oliver Neukum
0 siblings, 1 reply; 20+ messages in thread
From: Heikki Krogerus @ 2016-11-24 9:57 UTC (permalink / raw)
To: Guenter Roeck
Cc: Greg KH, Badhri Jagan Sridharan, Oliver Neukum, linux-kernel, linux-usb
On Wed, Nov 23, 2016 at 09:12:04PM -0800, Guenter Roeck wrote:
> Hello Heikki,
>
> On 11/22/2016 06:11 AM, Heikki Krogerus wrote:
> [ ... ]
> > +
> > +struct typec_port *typec_register_port(struct device *dev,
> > + const struct typec_capability *cap)
> > +{
> > + struct typec_port *port;
> > + int ret;
> > + int id;
> > +
> > + port = kzalloc(sizeof(*port), GFP_KERNEL);
> > + if (!port)
> > + return ERR_PTR(-ENOMEM);
> > +
> > + id = ida_simple_get(&typec_index_ida, 0, 0, GFP_KERNEL);
> > + if (id < 0) {
> > + kfree(port);
> > + return ERR_PTR(id);
> > + }
> > +
> > + port->prefer_role = TYPEC_NO_PREFERRED_ROLE;
> > +
>
> Following up on this:
>
> In our implementation, the default preferred role is determined by the
> low level driver (as, in my understanding, is suggested by the standard).
> This means that the ABI will report "no preferred role", unless user space
> overwrites it, even though there _is_ in fact a preferred role, and the
> low level driver will execute try.src or try.snk based on that role.
I'm not sure which standard are you referring? Try.SNK and Try.SRC are
optional mechanisms for *policy-based* role preference according to
the USB Type-C spec. The policy really should always come from the
user space in our case, but I don't think that rules out for example
initial role preferences coming from the lower level drivers.
We will need a way the OS can set the initial preference for every
port. Note that once we can support that, what ever the lower level
drivers request will be overridden by it. So if for example the
platform has preference for an initial role, we will simply ignore it
if the policy says otherwise.
> It might make sense to add the preferred role to struct typec_capability
> and get the initial value from there. If a low level driver does not want
> to specify it, it can easily set its value to TYPEC_NO_PREFERRED_ROLE.
Well, ideally the port drivers would not need to do anything if there
is no preference, but I don't think it's a problem. Since this is API,
I guess we can even change this later if we come up with a better way
of doing this. I'll add it.
Thanks,
--
heikki
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCHv12 2/3] usb: USB Type-C connector class
2016-11-24 9:57 ` Heikki Krogerus
@ 2016-11-28 10:19 ` Oliver Neukum
2016-11-28 14:23 ` Heikki Krogerus
0 siblings, 1 reply; 20+ messages in thread
From: Oliver Neukum @ 2016-11-28 10:19 UTC (permalink / raw)
To: Heikki Krogerus
Cc: Guenter Roeck, Badhri Jagan Sridharan, Greg KH, linux-kernel, linux-usb
On Thu, 2016-11-24 at 11:57 +0200, Heikki Krogerus wrote:
> On Wed, Nov 23, 2016 at 09:12:04PM -0800, Guenter Roeck wrote:
> > In our implementation, the default preferred role is determined by the
> > low level driver (as, in my understanding, is suggested by the standard).
> > This means that the ABI will report "no preferred role", unless user space
> > overwrites it, even though there _is_ in fact a preferred role, and the
> > low level driver will execute try.src or try.snk based on that role.
>
> I'm not sure which standard are you referring? Try.SNK and Try.SRC are
> optional mechanisms for *policy-based* role preference according to
> the USB Type-C spec. The policy really should always come from the
Not all that obvious. If you are looking at it from a distro view
point if you know that you are booting on basically a gadget, you'll
be happy to take the hint. And if the hardware knows it is better
as a sink or source, we should take the hint.
> user space in our case, but I don't think that rules out for example
> initial role preferences coming from the lower level drivers.
Indeed. That should not be a hindrance to submission and inclusion.
> We will need a way the OS can set the initial preference for every
> port. Note that once we can support that, what ever the lower level
> drivers request will be overridden by it. So if for example the
> platform has preference for an initial role, we will simply ignore it
> if the policy says otherwise.
Again, not obvious in a distro. I would actually prefer a module
parameter that would allow us to prefer try.src, as we know how
to be a master.
None of that should hinder submission and inclusion.
Regards
Oliver
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCHv12 2/3] usb: USB Type-C connector class
2016-11-28 10:19 ` Oliver Neukum
@ 2016-11-28 14:23 ` Heikki Krogerus
2016-11-28 20:11 ` Guenter Roeck
0 siblings, 1 reply; 20+ messages in thread
From: Heikki Krogerus @ 2016-11-28 14:23 UTC (permalink / raw)
To: Oliver Neukum
Cc: Guenter Roeck, Badhri Jagan Sridharan, Greg KH, linux-kernel, linux-usb
On Mon, Nov 28, 2016 at 11:19:32AM +0100, Oliver Neukum wrote:
> On Thu, 2016-11-24 at 11:57 +0200, Heikki Krogerus wrote:
> > On Wed, Nov 23, 2016 at 09:12:04PM -0800, Guenter Roeck wrote:
>
> > > In our implementation, the default preferred role is determined by the
> > > low level driver (as, in my understanding, is suggested by the standard).
> > > This means that the ABI will report "no preferred role", unless user space
> > > overwrites it, even though there _is_ in fact a preferred role, and the
> > > low level driver will execute try.src or try.snk based on that role.
> >
> > I'm not sure which standard are you referring? Try.SNK and Try.SRC are
> > optional mechanisms for *policy-based* role preference according to
> > the USB Type-C spec. The policy really should always come from the
>
> Not all that obvious. If you are looking at it from a distro view
> point if you know that you are booting on basically a gadget, you'll
> be happy to take the hint. And if the hardware knows it is better
> as a sink or source, we should take the hint.
>
> > user space in our case, but I don't think that rules out for example
> > initial role preferences coming from the lower level drivers.
>
> Indeed. That should not be a hindrance to submission and inclusion.
>
> > We will need a way the OS can set the initial preference for every
> > port. Note that once we can support that, what ever the lower level
> > drivers request will be overridden by it. So if for example the
> > platform has preference for an initial role, we will simply ignore it
> > if the policy says otherwise.
>
> Again, not obvious in a distro. I would actually prefer a module
> parameter that would allow us to prefer try.src, as we know how
> to be a master.
I would be happy with module parameter.
> None of that should hinder submission and inclusion.
It's already there in v13.
I better ping Greg already after rc1 this time, just so we don't start
the review again when we are already at rc5 (and I have forgotten
about this whole thing). I'll ask about the module parameter idea
then, though I'm guessing he won't like it. But maybe he has some
suggestions.
Thanks Oliver,
--
heikki
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCHv12 2/3] usb: USB Type-C connector class
2016-11-28 14:23 ` Heikki Krogerus
@ 2016-11-28 20:11 ` Guenter Roeck
2016-11-29 7:40 ` Oliver Neukum
2016-11-29 12:59 ` Heikki Krogerus
0 siblings, 2 replies; 20+ messages in thread
From: Guenter Roeck @ 2016-11-28 20:11 UTC (permalink / raw)
To: Heikki Krogerus
Cc: Oliver Neukum, Badhri Jagan Sridharan, Greg KH, linux-kernel, linux-usb
On Mon, Nov 28, 2016 at 04:23:23PM +0200, Heikki Krogerus wrote:
> On Mon, Nov 28, 2016 at 11:19:32AM +0100, Oliver Neukum wrote:
> > On Thu, 2016-11-24 at 11:57 +0200, Heikki Krogerus wrote:
> > > On Wed, Nov 23, 2016 at 09:12:04PM -0800, Guenter Roeck wrote:
> >
> > > > In our implementation, the default preferred role is determined by the
> > > > low level driver (as, in my understanding, is suggested by the standard).
> > > > This means that the ABI will report "no preferred role", unless user space
> > > > overwrites it, even though there _is_ in fact a preferred role, and the
> > > > low level driver will execute try.src or try.snk based on that role.
> > >
> > > I'm not sure which standard are you referring? Try.SNK and Try.SRC are
> > > optional mechanisms for *policy-based* role preference according to
> > > the USB Type-C spec. The policy really should always come from the
> >
The Type-C specification states (in 4.5.2.2.11):
"Note: if both Try.SRC and Try.SNK mechanisms are implemented, only one shall be
enabled by the port at any given time. Deciding which of these two mechanisms
is enabled is product design-specific."
I read into this:
- The class code can not assume that either of those mechanisms are implemented.
- "product-design specific" means that the product designer determines which of
the two mechanisms (if any) is enabled. While not explicitly stated, I would
assume this to be set either by hardware or via devicetree / ACPI, and not
from user space.
I don't mind to have user space control; all I am asking for is to have the
means for lower level code (which is the most likely entity to know about
product design) to be able to inform higher layers about its initial
preferences. We have this now, so I am happy.
> > Not all that obvious. If you are looking at it from a distro view
> > point if you know that you are booting on basically a gadget, you'll
> > be happy to take the hint. And if the hardware knows it is better
> > as a sink or source, we should take the hint.
> >
> > > user space in our case, but I don't think that rules out for example
> > > initial role preferences coming from the lower level drivers.
> >
> > Indeed. That should not be a hindrance to submission and inclusion.
> >
> > > We will need a way the OS can set the initial preference for every
> > > port. Note that once we can support that, what ever the lower level
> > > drivers request will be overridden by it. So if for example the
> > > platform has preference for an initial role, we will simply ignore it
> > > if the policy says otherwise.
> >
> > Again, not obvious in a distro. I would actually prefer a module
> > parameter that would allow us to prefer try.src, as we know how
> > to be a master.
>
> I would be happy with module parameter.
>
Personally I don't really care about a module parameter; as mentioned above,
I would expect the preference, if it needs to be selectable, to be configured
with devicetree or ACPI properties (or by a platform driver which sets a device
property).
Thanks,
Guenter
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCHv12 2/3] usb: USB Type-C connector class
2016-11-28 20:11 ` Guenter Roeck
@ 2016-11-29 7:40 ` Oliver Neukum
2016-11-29 12:59 ` Heikki Krogerus
1 sibling, 0 replies; 20+ messages in thread
From: Oliver Neukum @ 2016-11-29 7:40 UTC (permalink / raw)
To: Guenter Roeck
Cc: Heikki Krogerus, Badhri Jagan Sridharan, Greg KH, linux-kernel,
linux-usb
On Mon, 2016-11-28 at 12:11 -0800, Guenter Roeck wrote:
> On Mon, Nov 28, 2016 at 04:23:23PM +0200, Heikki Krogerus wrote:
> > On Mon, Nov 28, 2016 at 11:19:32AM +0100, Oliver Neukum wrote:
> The Type-C specification states (in 4.5.2.2.11):
>
> "Note: if both Try.SRC and Try.SNK mechanisms are implemented, only one shall be
> enabled by the port at any given time. Deciding which of these two mechanisms
> is enabled is product design-specific."
>
> I read into this:
> - The class code can not assume that either of those mechanisms are implemented.
Total agreement
> - "product-design specific" means that the product designer determines which of
> the two mechanisms (if any) is enabled. While not explicitly stated, I would
> assume this to be set either by hardware or via devicetree / ACPI, and not
> from user space.
I read this as spec-speak for "not our problem"
> I don't mind to have user space control; all I am asking for is to have the
> means for lower level code (which is the most likely entity to know about
> product design) to be able to inform higher layers about its initial
> preferences. We have this now, so I am happy.
So am I.
> Personally I don't really care about a module parameter; as mentioned above,
> I would expect the preference, if it needs to be selectable, to be configured
> with devicetree or ACPI properties (or by a platform driver which sets a device
> property).
Well, as a distro for generic desktops and servers you will face an XHCI
on PCI and UCSI telling you that your ports can express preferences. Now
deal with it. And it must be said that such distros have over a decade
of experience in acting as a master. The slave capability is less well
developed. We'd like to be master. And if possible we'd like to avoid
a later switch of roles, so the choice should be easily made in initrd.
Regards
Oliver
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCHv12 2/3] usb: USB Type-C connector class
2016-11-28 20:11 ` Guenter Roeck
2016-11-29 7:40 ` Oliver Neukum
@ 2016-11-29 12:59 ` Heikki Krogerus
2016-11-29 13:20 ` Greg KH
1 sibling, 1 reply; 20+ messages in thread
From: Heikki Krogerus @ 2016-11-29 12:59 UTC (permalink / raw)
To: Guenter Roeck
Cc: Oliver Neukum, Badhri Jagan Sridharan, Greg KH, linux-kernel, linux-usb
Hi Guenter,
On Mon, Nov 28, 2016 at 12:11:43PM -0800, Guenter Roeck wrote:
> Personally I don't really care about a module parameter; as mentioned above,
> I would expect the preference, if it needs to be selectable, to be configured
> with devicetree or ACPI properties (or by a platform driver which sets a device
> property).
Unfortunately we can not assume the firmware to be always correct.
Companies love to recycle the firmware. We are going to see products
from a company X that should prefer source role, a desktop for
example, but still give the OS a device property that says otherwise.
The reason for that is most likely because the previous product from
that company was some kind of mobile device.
So IMHO we need some way for the OS to override this thing eventually.
If not module parameters, then something else. The other option is
board specific quirks, and I would really prefer to avoid those if we
can.
Thanks,
--
heikki
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCHv12 2/3] usb: USB Type-C connector class
2016-11-29 12:59 ` Heikki Krogerus
@ 2016-11-29 13:20 ` Greg KH
2016-11-29 13:48 ` Oliver Neukum
0 siblings, 1 reply; 20+ messages in thread
From: Greg KH @ 2016-11-29 13:20 UTC (permalink / raw)
To: Heikki Krogerus
Cc: Guenter Roeck, Oliver Neukum, Badhri Jagan Sridharan,
linux-kernel, linux-usb
On Tue, Nov 29, 2016 at 02:59:58PM +0200, Heikki Krogerus wrote:
> Hi Guenter,
>
> On Mon, Nov 28, 2016 at 12:11:43PM -0800, Guenter Roeck wrote:
> > Personally I don't really care about a module parameter; as mentioned above,
> > I would expect the preference, if it needs to be selectable, to be configured
> > with devicetree or ACPI properties (or by a platform driver which sets a device
> > property).
>
> Unfortunately we can not assume the firmware to be always correct.
> Companies love to recycle the firmware. We are going to see products
> from a company X that should prefer source role, a desktop for
> example, but still give the OS a device property that says otherwise.
> The reason for that is most likely because the previous product from
> that company was some kind of mobile device.
>
> So IMHO we need some way for the OS to override this thing eventually.
> If not module parameters, then something else. The other option is
> board specific quirks, and I would really prefer to avoid those if we
> can.
Whatever it is, it is NOT going to be a module parameter, sorry, that
ship has long sailed and will not be coming back. This isn't the 1990's
anymore...
thanks,
greg k-h
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCHv12 2/3] usb: USB Type-C connector class
2016-11-29 13:20 ` Greg KH
@ 2016-11-29 13:48 ` Oliver Neukum
2016-11-29 14:02 ` Greg KH
0 siblings, 1 reply; 20+ messages in thread
From: Oliver Neukum @ 2016-11-29 13:48 UTC (permalink / raw)
To: Greg KH
Cc: Heikki Krogerus, Badhri Jagan Sridharan, Guenter Roeck,
linux-kernel, linux-usb
On Tue, 2016-11-29 at 14:20 +0100, Greg KH wrote:
> On Tue, Nov 29, 2016 at 02:59:58PM +0200, Heikki Krogerus wrote:
> > Hi Guenter,
> >
> > On Mon, Nov 28, 2016 at 12:11:43PM -0800, Guenter Roeck wrote:
> > > Personally I don't really care about a module parameter; as mentioned above,
> > > I would expect the preference, if it needs to be selectable, to be configured
> > > with devicetree or ACPI properties (or by a platform driver which sets a device
> > > property).
> >
> > Unfortunately we can not assume the firmware to be always correct.
> > Companies love to recycle the firmware. We are going to see products
> > from a company X that should prefer source role, a desktop for
> > example, but still give the OS a device property that says otherwise.
> > The reason for that is most likely because the previous product from
> > that company was some kind of mobile device.
> >
> > So IMHO we need some way for the OS to override this thing eventually.
> > If not module parameters, then something else. The other option is
> > board specific quirks, and I would really prefer to avoid those if we
> > can.
>
> Whatever it is, it is NOT going to be a module parameter, sorry, that
> ship has long sailed and will not be coming back. This isn't the 1990's
> anymore...
Do you have a sensible alternative that works at boot time?
Regards
Oliver
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCHv12 2/3] usb: USB Type-C connector class
2016-11-29 13:48 ` Oliver Neukum
@ 2016-11-29 14:02 ` Greg KH
0 siblings, 0 replies; 20+ messages in thread
From: Greg KH @ 2016-11-29 14:02 UTC (permalink / raw)
To: Oliver Neukum
Cc: Heikki Krogerus, Badhri Jagan Sridharan, Guenter Roeck,
linux-kernel, linux-usb
On Tue, Nov 29, 2016 at 02:48:46PM +0100, Oliver Neukum wrote:
> On Tue, 2016-11-29 at 14:20 +0100, Greg KH wrote:
> > On Tue, Nov 29, 2016 at 02:59:58PM +0200, Heikki Krogerus wrote:
> > > Hi Guenter,
> > >
> > > On Mon, Nov 28, 2016 at 12:11:43PM -0800, Guenter Roeck wrote:
> > > > Personally I don't really care about a module parameter; as mentioned above,
> > > > I would expect the preference, if it needs to be selectable, to be configured
> > > > with devicetree or ACPI properties (or by a platform driver which sets a device
> > > > property).
> > >
> > > Unfortunately we can not assume the firmware to be always correct.
> > > Companies love to recycle the firmware. We are going to see products
> > > from a company X that should prefer source role, a desktop for
> > > example, but still give the OS a device property that says otherwise.
> > > The reason for that is most likely because the previous product from
> > > that company was some kind of mobile device.
> > >
> > > So IMHO we need some way for the OS to override this thing eventually.
> > > If not module parameters, then something else. The other option is
> > > board specific quirks, and I would really prefer to avoid those if we
> > > can.
> >
> > Whatever it is, it is NOT going to be a module parameter, sorry, that
> > ship has long sailed and will not be coming back. This isn't the 1990's
> > anymore...
>
> Do you have a sensible alternative that works at boot time?
I'm for a device tree or acpi setting, as this really is a platform
thing, it is the only place that should "know" this type of problem, so
that is where it can be 'specified'.
Yes, firmware is "hard", but it gets really really tiring constantly
having to paper over firmware and hardware bugs in the kernel just
because we seem to be the ones that are willing to actually fix problems
that others cause.
thanks,
greg k-h
^ permalink raw reply [flat|nested] 20+ messages in thread