Commit fab92884 authored by Heikki Krogerus's avatar Heikki Krogerus Committed by Greg Kroah-Hartman

usb: USB Type-C connector class

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: default avatarHeikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: default avatarMika Westerberg <mika.westerberg@linux.intel.com>
Reviewed-and-Tested-by: default avatarFelipe Balbi <felipe.balbi@linux.intel.com>
Tested-by: default avatarGuenter Roeck <linux@roeck-us.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent e1fe7b6a
USB Type-C port devices (eg. /sys/class/typec/port0/)
What: /sys/class/typec/<port>/data_role
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
The supported USB data roles. 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. The current role is show in brackets,
for example "[host] device" when DRP port is in host mode.
Valid values: host, device
What: /sys/class/typec/<port>/power_role
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
The supported power roles. 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. The current role is show in
brackets, for example "[source] sink" when in source mode.
Valid values: source, sink
What: /sys/class/typec/<port>/vconn_source
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
Shows is the port VCONN Source. This attribute can be used to
request VCONN swap to change the VCONN Source during connection
when both the port and the partner support USB Power Delivery.
Swapping is supported as synchronous operation, so write(2) to
the attribute will not return until the operation has finished.
The attribute is notified about VCONN source changes so that
poll(2) on the attribute wakes up. Change on VCONN source also
generates uevent KOBJ_CHANGE.
Valid values:
- "no" when the port is not the VCONN Source
- "yes" when the port is the VCONN Source
What: /sys/class/typec/<port>/power_operation_mode
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
Shows the current power operational mode the port is in. The
power operation mode means current level for VBUS. In case USB
Power Delivery communication is used for negotiating the levels,
power operation mode should show "usb_power_delivery".
Valid values:
- default
- 1.5A
- 3.0A
- usb_power_delivery
What: /sys/class/typec/<port>/preferred_role
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
The user space can notify the driver about the preferred role.
It should be handled as enabling of Try.SRC or Try.SNK, as
defined in USB Type-C specification, in the port drivers. By
default the preferred role should come from the platform.
Valid values: source, sink, none (to remove preference)
What: /sys/class/typec/<port>/supported_accessory_modes
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
Space separated list of accessory modes, defined in the USB
Type-C specification, the port supports.
What: /sys/class/typec/<port>/usb_power_delivery_revision
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
Revision number of the supported USB Power Delivery
specification, or 0 when USB Power Delivery is not supported.
What: /sys/class/typec/<port>/usb_typec_revision
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
Revision number of the supported USB Type-C specification.
USB Type-C partner devices (eg. /sys/class/typec/port0-partner/)
What: /sys/class/typec/<port>-partner/accessory_mode
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
Shows the Accessory Mode name when the partner is an Accessory.
The Accessory Modes are defined in USB Type-C Specification.
What: /sys/class/typec/<port>-partner/supports_usb_power_delivery
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
Shows if the partner supports USB Power Delivery communication:
Valid values: yes, no
What: /sys/class/typec/<port>-partner>/identity/
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
This directory appears only if the port device driver is capable
of showing the result of Discover Identity USB power delivery
command. That will not always be possible even when USB power
delivery is supported, for example when USB power delivery
communication for the port is mostly handled in firmware. If the
directory exists, it will have an attribute file for every VDO
in Discover Identity command result.
What: /sys/class/typec/<port>-partner/identity/id_header
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
ID Header VDO part of Discover Identity command result. The
value will show 0 until Discover Identity command result becomes
available. The value can be polled.
What: /sys/class/typec/<port>-partner/identity/cert_stat
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
Cert Stat VDO part of Discover Identity command result. The
value will show 0 until Discover Identity command result becomes
available. The value can be polled.
What: /sys/class/typec/<port>-partner/identity/product
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
Product VDO part of Discover Identity command result. The value
will show 0 until Discover Identity command result becomes
available. The value can be polled.
USB Type-C cable devices (eg. /sys/class/typec/port0-cable/)
Note: Electronically Marked Cables will have a device also for one cable plug
(eg. /sys/class/typec/port0-plug0). If the cable is active and has also SOP
Double Prime controller (USB Power Deliver specification ch. 2.4) it will have
second device also for the other plug. Both plugs may have alternate modes as
described in USB Type-C and USB Power Delivery specifications.
What: /sys/class/typec/<port>-cable/type
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
Shows if the cable is active.
Valid values: active, passive
What: /sys/class/typec/<port>-cable/plug_type
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
Shows type of the plug on the cable:
- type-a - Standard A
- type-b - Standard B
- type-c
- captive
What: /sys/class/typec/<port>-cable/identity/
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
This directory appears only if the port device driver is capable
of showing the result of Discover Identity USB power delivery
command. That will not always be possible even when USB power
delivery is supported. If the directory exists, it will have an
attribute for every VDO returned by Discover Identity command.
What: /sys/class/typec/<port>-cable/identity/id_header
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
ID Header VDO part of Discover Identity command result. The
value will show 0 until Discover Identity command result becomes
available. The value can be polled.
What: /sys/class/typec/<port>-cable/identity/cert_stat
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
Cert Stat VDO part of Discover Identity command result. The
value will show 0 until Discover Identity command result becomes
available. The value can be polled.
What: /sys/class/typec/<port>-cable/identity/product
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
Product VDO part of Discover Identity command result. The value
will show 0 until Discover Identity command result becomes
available. The value can be polled.
Alternate Mode devices.
The alternate modes will have Standard or Vendor ID (SVID) assigned by USB-IF.
The ports, partners and cable plugs can have alternate modes. A supported SVID
will consist of a set of modes. Every SVID a port/partner/plug supports will
have a device created for it, and every supported mode for a supported SVID will
have its own directory under that device. Below <dev> refers to the device for
the alternate mode.
What: /sys/class/typec/<port|partner|cable>/<dev>/svid
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
The SVID (Standard or Vendor ID) assigned by USB-IF for this
alternate mode.
What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
Every supported mode will have its own directory. The name of
a mode will be "mode<index>" (for example mode1), where <index>
is the actual index to the mode VDO returned by Discover Modes
USB power delivery command.
What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/description
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
Shows description of the mode. The description is optional for
the drivers, just like with the Billboard Devices.
What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/vdo
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
Shows the VDO in hexadecimal returned by Discover Modes command
for this mode.
What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/active
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
Shows if the mode is active or not. The attribute can be used
for entering/exiting the mode with partners and cable plugs, and
with the port alternate modes it can be used for disabling
support for specific alternate modes. Entering/exiting modes is
supported as synchronous operation so write(2) to the attribute
does not return until the enter/exit mode operation has
finished. The attribute is notified when the mode is
entered/exited so poll(2) on the attribute wakes up.
Entering/exiting a mode will also generate uevent KOBJ_CHANGE.
Valid values: yes, no
What: /sys/class/typec/<port>/<dev>/mode<index>/supported_roles
Date: April 2017
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
Space separated list of the supported roles.
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.
Valid values: source, sink
USB Type-C connector class
==========================
Introduction
------------
The typec class is meant for describing the USB Type-C ports in a system to the
user space in unified fashion. The class is designed to provide nothing else
except the user space interface implementation in hope that it can be utilized
on as many platforms as possible.
The platforms are expected to register every USB Type-C port they have with the
class. In a normal case the registration will be done by a USB Type-C or PD PHY
driver, but it may be a driver for firmware interface such as UCSI, driver for
USB PD controller or even driver for Thunderbolt3 controller. This document
considers the component registering the USB Type-C ports with the class as "port
driver".
On top of showing the capabilities, the class also offer user space control over
the roles and alternate modes of ports, partners and cable plugs when the port
driver is capable of supporting those features.
The class provides an API for the port drivers described in this document. The
attributes are described in Documentation/ABI/testing/sysfs-class-typec.
User space interface
--------------------
Every port will be presented as its own device under /sys/class/typec/. The
first port will be named "port0", the second "port1" and so on.
When connected, the partner will be presented also as its own device under
/sys/class/typec/. The parent of the partner device will always be the port it
is attached to. The partner attached to port "port0" will be named
"port0-partner". Full path to the device would be
/sys/class/typec/port0/port0-partner/.
The cable and the two plugs on it may also be optionally presented as their own
devices under /sys/class/typec/. The cable attached to the port "port0" port
will be named port0-cable and the plug on the SOP Prime end (see USB Power
Delivery Specification ch. 2.4) will be named "port0-plug0" and on the SOP
Double Prime end "port0-plug1". The parent of a cable will always be the port,
and the parent of the cable plugs will always be the cable.
If the port, partner or cable plug supports Alternate Modes, every supported
Alternate Mode SVID will have their own device describing them. Note that the
Alternate Mode devices will not be attached to the typec class. The parent of an
alternate mode will be the device that supports it, so for example an alternate
mode of port0-partner will be presented under /sys/class/typec/port0-partner/.
Every mode that is supported will have its own group under the Alternate Mode
device named "mode<index>", for example /sys/class/typec/port0/<alternate
mode>/mode1/. The requests for entering/exiting a mode can be done with "active"
attribute file in that group.
Driver 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:
.. kernel-doc:: drivers/usb/typec/typec.c
:functions: typec_register_port typec_unregister_port
When registering the ports, the prefer_role member in struct typec_capability
deserves special notice. If the port that is being registered does not have
initial role preference, which means the port does not execute Try.SNK or
Try.SRC by default, the member must have value TYPEC_NO_PREFERRED_ROLE.
Otherwise if the port executes Try.SNK by default, the member must have value
TYPEC_DEVICE, and with Try.SRC the value must be TYPEC_HOST.
Registering Partners
~~~~~~~~~~~~~~~~~~~~
After successful connection of a partner, the port driver needs to register the
partner with the class. Details about the partner need to be described in struct
typec_partner_desc. The class copies the details of the partner during
registration. The class offers the following API for registering/unregistering
partners.
.. kernel-doc:: drivers/usb/typec/typec.c
:functions: typec_register_partner typec_unregister_partner
The class will provide a handle to struct typec_partner if the registration was
successful, or NULL.
If the partner is USB Power Delivery capable, and the port driver is able to
show the result of Discover Identity command, the partner descriptor structure
should include handle to struct usb_pd_identity instance. The class will then
create a sysfs directory for the identity under the partner device. The result
of Discover Identity command can then be reported with the following API:
.. kernel-doc:: drivers/usb/typec/typec.c
:functions: typec_partner_set_identity
Registering Cables
~~~~~~~~~~~~~~~~~~
After successful connection of a cable that supports USB Power Delivery
Structured VDM "Discover Identity", the port driver needs to register the cable
and one or two plugs, depending if there is CC Double Prime controller present
in the cable or not. So a cable capable of SOP Prime communication, but not SOP
Double Prime communication, should only have one plug registered. For more
information about SOP communication, please read chapter about it from the
latest USB Power Delivery specification.
The plugs are represented as their own devices. The cable is registered first,
followed by registration of the cable plugs. The cable will be the parent device
for the plugs. Details about the cable need to be described in struct
typec_cable_desc and about a plug in struct typec_plug_desc. The class copies
the details during registration. The class offers the following API for
registering/unregistering cables and their plugs:
.. kernel-doc:: drivers/usb/typec/typec.c
:functions: typec_register_cable typec_unregister_cable typec_register_plug
typec_unregister_plug
The class will provide a handle to struct typec_cable and struct typec_plug if
the registration is successful, or NULL if it isn't.
If the cable is USB Power Delivery capable, and the port driver is able to show
the result of Discover Identity command, the cable descriptor structure should
include handle to struct usb_pd_identity instance. The class will then create a
sysfs directory for the identity under the cable device. The result of Discover
Identity command can then be reported with the following API:
.. kernel-doc:: drivers/usb/typec/typec.c
:functions: typec_cable_set_identity
Notifications
~~~~~~~~~~~~~
When the partner has executed a role change, or when the default roles change
during connection of a partner or cable, the port driver must use the following
APIs to report it to the class:
.. kernel-doc:: drivers/usb/typec/typec.c
:functions: typec_set_data_role typec_set_pwr_role typec_set_vconn_role
typec_set_pwr_opmode
Alternate Modes
~~~~~~~~~~~~~~~
USB Type-C ports, partners and cable plugs may support Alternate Modes. Each
Alternate Mode will have identifier called SVID, which is either a Standard ID
given by USB-IF or vendor ID, and each supported SVID can have 1 - 6 modes. The
class provides struct typec_mode_desc for describing individual mode of a SVID,
and struct typec_altmode_desc which is a container for all the supported modes.
Ports that support Alternate Modes need to register each SVID they support with
the following API:
.. kernel-doc:: drivers/usb/typec/typec.c
:functions: typec_port_register_altmode
If a partner or cable plug provides a list of SVIDs as response to USB Power
Delivery Structured VDM Discover SVIDs message, each SVID needs to be
registered.
API for the partners:
.. kernel-doc:: drivers/usb/typec/typec.c
:functions: typec_partner_register_altmode
API for the Cable Plugs:
.. kernel-doc:: drivers/usb/typec/typec.c
:functions: typec_plug_register_altmode
So ports, partners and cable plugs will register the alternate modes with their
own functions, but the registration will always return a handle to struct
typec_altmode on success, or NULL. The unregistration will happen with the same
function:
.. kernel-doc:: drivers/usb/typec/typec.c
:functions: typec_unregister_altmode
If a partner or cable plug enters or exits a mode, the port driver needs to
notify the class with the following API:
.. kernel-doc:: drivers/usb/typec/typec.c
:functions: typec_altmode_update_active
......@@ -13100,6 +13100,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.rst
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
......
......@@ -162,6 +162,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
......
......@@ -62,3 +62,5 @@ obj-$(CONFIG_USB_GADGET) += gadget/
obj-$(CONFIG_USB_COMMON) += common/
obj-$(CONFIG_USBIP_CORE) += usbip/
obj-$(CONFIG_TYPEC) += typec/
menu "USB Power Delivery and Type-C drivers"
config TYPEC
tristate
endmenu
obj-$(CONFIG_TYPEC) += typec.o
/*
* USB Type-C Connector Class
*
* Copyright (C) 2017, Intel Corporation
* Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/usb/typec.h>
struct typec_mode {
int index;
u32 vdo;
char *desc;
enum typec_port_type roles;
struct typec_altmode *alt_mode;
unsigned int active:1;
char group_name[6];
struct attribute_group group;
struct attribute *attrs[5];
struct device_attribute vdo_attr;
struct device_attribute desc_attr;
struct device_attribute active_attr;
struct device_attribute roles_attr;
};
struct typec_altmode {
struct device dev;
u16 svid;
int n_modes;
struct typec_mode modes[ALTMODE_MAX_MODES];
const struct attribute_group *mode_groups[ALTMODE_MAX_MODES];
};
struct typec_plug {
struct device dev;
enum typec_plug_index index;
};
struct typec_cable {
struct device dev;
enum typec_plug_type type;
struct usb_pd_identity *identity;
unsigned int active:1;
};
struct typec_partner {
struct device dev;
unsigned int usb_pd:1;
struct usb_pd_identity *identity;
enum typec_accessory accessory;
};
struct typec_port {
unsigned int id;
struct device dev;
int prefer_role;
enum typec_data_role data_role;
enum typec_role pwr_role;
enum typec_role vconn_role;
enum typec_pwr_opmode pwr_opmode;
const struct typec_capability *cap;
};
#define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev)
#define to_typec_plug(_dev_) container_of(_dev_, struct typec_plug, dev)
#define to_typec_cable(_dev_) container_of(_dev_, struct typec_cable, dev)
#define to_typec_partner(_dev_) container_of(_dev_, struct typec_partner, dev)
#define to_altmode(_dev_) container_of(_dev_, struct typec_altmode, dev)
static const struct device_type typec_partner_dev_type;
static const struct device_type typec_cable_dev_type;
static const struct device_type typec_plug_dev_type;
static const struct device_type typec_port_dev_type;
#define is_typec_partner(_dev_) (_dev_->type == &typec_partner_dev_type)
#define is_typec_cable(_dev_) (_dev_->type == &typec_cable_dev_type)
#define is_typec_plug(_dev_) (_dev_->type == &typec_plug_dev_type)
#define is_typec_port(_dev_) (_dev_->type == &typec_port_dev_type)
static DEFINE_IDA(typec_index_ida);
static struct class *typec_class;
/* Common attributes */
static const char * const typec_accessory_modes[] = {
[TYPEC_ACCESSORY_NONE] = "none",
[TYPEC_ACCESSORY_AUDIO] = "analog_audio",
[TYPEC_ACCESSORY_DEBUG] = "debug",
};
static struct usb_pd_identity *get_pd_identity(struct device *dev)
{
if (is_typec_partner(dev)) {
struct typec_partner *partner = to_typec_partner(dev);
return partner->identity;
} else if (is_typec_cable(dev)) {
struct typec_cable *cable = to_typec_cable(dev);
return cable->identity;
}
return NULL;
}
static ssize_t id_header_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct usb_pd_identity *id = get_pd_identity(dev);
return sprintf(buf, "0x%08x\n", id->id_header);
}
static DEVICE_ATTR_RO(id_header);
static ssize_t cert_stat_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct usb_pd_identity *id = get_pd_identity(dev);
return sprintf(buf, "0x%08x\n", id->cert_stat);
}
static DEVICE_ATTR_RO(cert_stat);
static ssize_t product_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct usb_pd_identity *id = get_pd_identity(dev);
return sprintf(buf, "0x%08x\n", id->product);
}
static DEVICE_ATTR_RO(product);
static struct attribute *usb_pd_id_attrs[] = {
&dev_attr_id_header.attr,
&dev_attr_cert_stat.attr,
&dev_attr_product.attr,
NULL
};
static const struct attribute_group usb_pd_id_group = {
.name = "identity",
.attrs = usb_pd_id_attrs,
};
static const struct attribute_group *usb_pd_id_groups[] = {
&usb_pd_id_group,
NULL,
};
static void typec_report_identity(struct device *dev)
{
sysfs_notify(&dev->kobj, "identity", "id_header");
sysfs_notify(&dev->kobj, "identity", "cert_stat");
sysfs_notify(&dev->kobj, "identity", "product");
}
/* ------------------------------------------------------------------------- */
/* Alternate Modes */
/**
* typec_altmode_update_active - Report Enter/Exit mode
* @alt: Handle to the alternate mode
* @mode: Mode index
* @active: True when the mode has been entered
*
* If a partner or cable plug executes Enter/Exit Mode command successfully, the
* drivers use this routine to report the updated state of the mode.
*/
void typec_altmode_update_active(struct typec_altmode *alt, int mode,
bool active)
{
struct typec_mode *m = &alt->modes[mode];
char dir[6];
if (m->active == active)
return;
m->active = active;
snprintf(dir, sizeof(dir), "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 handle to the port that a cable plug or partner with @alt is
* connected to.
*/
struct typec_port *typec_altmode2port(struct typec_altmode *alt)
{
if (is_typec_plug(alt->dev.parent))
return to_typec_port(alt->dev.parent->parent->parent);
if (is_typec_partner(alt->dev.parent))
return to_typec_port(alt->dev.parent->parent);
if (is_typec_port(alt->dev.parent))
return to_typec_port(alt->dev.parent);
return NULL;
}
EXPORT_SYMBOL_GPL(typec_altmode2port);
static 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, "%s\n", mode->active ? "yes" : "no");
}
static ssize_t
typec_altmode_active_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t size)
{
struct typec_mode *mode = container_of(attr, struct typec_mode,
active_attr);
struct typec_port *port = typec_altmode2port(mode->alt_mode);
bool activate;
int ret;
if (!port->cap->activate_mode)
return -EOPNOTSUPP;
ret = kstrtobool(buf, &activate);
if (ret)
return ret;
ret = port->cap->activate_mode(port->cap, mode->index, activate);
if (ret)
return ret;
return size;
}
static ssize_t
typec_altmode_roles_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct typec_mode *mode = container_of(attr, struct typec_mode,
roles_attr);
ssize_t ret;
switch (mode->roles) {
case TYPEC_PORT_DFP:
ret = sprintf(buf, "source\n");
break;
case TYPEC_PORT_UFP:
ret = sprintf(buf, "sink\n");
break;
case TYPEC_PORT_DRP:
default:
ret = sprintf(buf, "source sink\n");
break;
}
return ret;
}
static void typec_init_modes(struct typec_altmode *alt,
struct typec_mode_desc *desc, bool is_port)
{
int i;
for (i = 0; i < alt->n_modes; i++, desc++) {
struct typec_mode *mode = &alt->modes[i];
/* Not considering the human readable description critical */
mode->desc = kstrdup(desc->desc, GFP_KERNEL);
if (desc->desc && !mode->desc)
dev_err(&alt->dev, "failed to copy mode%d desc\n", i);
mode->alt_mode = alt;
mode->vdo = desc->vdo;
mode->roles = desc->roles;
mode->index = desc->index;
sprintf(mode->group_name, "mode%d", desc->index);
sysfs_attr_init(&mode->vdo_attr.attr);
mode->vdo_attr.attr.name = "vdo";
mode->vdo_attr.attr.mode = 0444;
mode->vdo_attr.show = typec_altmode_vdo_show;
sysfs_attr_init(&mode->desc_attr.attr);
mode->desc_attr.attr.name = "description";
mode->desc_attr.attr.mode = 0444;
mode->desc_attr.show = typec_altmode_desc_show;
sysfs_attr_init(&mode->active_attr.attr);
mode->active_attr.attr.name = "active";
mode->active_attr.attr.mode = 0644;
mode->active_attr.show = typec_altmode_active_show;
mode->active_attr.store = typec_altmode_active_store;
mode->attrs[0] = &mode->vdo_attr.attr;
mode->attrs[1] = &mode->desc_attr.attr;
mode->attrs[2] = &mode->active_attr.attr;
/* With ports, list the roles that the mode is supported with */
if (is_port) {
sysfs_attr_init(&mode->roles_attr.attr);
mode->roles_attr.attr.name = "supported_roles";
mode->roles_attr.attr.mode = 0444;
mode->roles_attr.show = typec_altmode_roles_show;
mode->attrs[3] = &mode->roles_attr.attr;
}
mode->group.attrs = mode->attrs;
mode->group.name = mode->group_name;
alt->mode_groups[i] = &mode->group;
}
}
static ssize_t svid_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct typec_altmode *alt = to_altmode(dev);
return sprintf(buf, "%04x\n", alt->svid);
}
static DEVICE_ATTR_RO(svid);
static struct attribute *typec_altmode_attrs[] = {
&dev_attr_svid.attr,
NULL
};
ATTRIBUTE_GROUPS(typec_altmode);
static void typec_altmode_release(struct device *dev)
{
struct typec_altmode *alt = to_altmode(dev);
int i;
for (i = 0; i < alt->n_modes; i++)
kfree(alt->modes[i].desc);
kfree(alt);
}
static const struct device_type typec_altmode_dev_type = {
.name = "typec_alternate_mode",
.groups = typec_altmode_groups,
.release = typec_altmode_release,
};
static struct typec_altmode *
typec_register_altmode(struct device *parent, struct typec_altmode_desc *desc)
{
struct typec_altmode *alt;
int ret;
alt = kzalloc(sizeof(*alt), GFP_KERNEL);
if (!alt)
return NULL;
alt->svid = desc->svid;
alt->n_modes = desc->n_modes;
typec_init_modes(alt, desc->modes, is_typec_port(parent));
alt->dev.parent = parent;
alt->dev.groups = alt->mode_groups;
alt->dev.type = &typec_altmode_dev_type;
dev_set_name(&alt->dev, "svid-%04x", alt->svid);
ret = device_register(&alt->dev);
if (ret) {
dev_err(parent, "failed to register alternate mode (%d)\n",
ret);
put_device(&alt->dev);
return NULL;
}
return alt;
}
/**
* typec_unregister_altmode - Unregister Alternate Mode
* @alt: The alternate mode to be unregistered
*
* Unregister device created with typec_partner_register_altmode(),
* typec_plug_register_altmode() or typec_port_register_altmode().
*/
void typec_unregister_altmode(struct typec_altmode *alt)
{
if (alt)
device_unregister(&alt->dev);
}
EXPORT_SYMBOL_GPL(typec_unregister_altmode);
/* ------------------------------------------------------------------------- */
/* Type-C Partners */
static ssize_t accessory_mode_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct typec_partner *p = to_typec_partner(dev);
return sprintf(buf, "%s\n", typec_accessory_modes[p->accessory]);
}
static DEVICE_ATTR_RO(accessory_mode);
static ssize_t supports_usb_power_delivery_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct typec_partner *p = to_typec_partner(dev);
return sprintf(buf, "%s\n", p->usb_pd ? "yes" : "no");
}
static DEVICE_ATTR_RO(supports_usb_power_delivery);
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_partner *partner = to_typec_partner(dev);
kfree(partner);
}
static const struct device_type typec_partner_dev_type = {
.name = "typec_partner",
.groups = typec_partner_groups,
.release = typec_partner_release,
};
/**
* typec_partner_set_identity - Report result from Discover Identity command
* @partner: The partner updated identity values
*
* This routine is used to report that the result of Discover Identity USB power
* delivery command has become available.
*/
int typec_partner_set_identity(struct typec_partner *partner)
{
if (!partner->identity)
return -EINVAL;
typec_report_identity(&partner->dev);
return 0;
}
EXPORT_SYMBOL_GPL(typec_partner_set_identity);
/**
* typec_partner_register_altmode - Register USB Type-C Partner Alternate Mode
* @partner: USB Type-C Partner that supports the alternate mode
* @desc: Description of the alternate mode
*
* This routine is used to register each alternate mode individually that
* @partner has listed in response to Discover SVIDs command. The modes for a
* SVID listed in response to Discover Modes command need to be listed in an
* array in @desc.
*
* Returns handle to the alternate mode on success or NULL on failure.
*/
struct typec_altmode *
typec_partner_register_altmode(struct typec_partner *partner,
struct typec_altmode_desc *desc)
{
return typec_register_altmode(&partner->dev, desc);
}
EXPORT_SYMBOL_GPL(typec_partner_register_altmode);
/**
* typec_register_partner - Register a USB Type-C Partner
* @port: The USB Type-C Port the partner is connected to
* @desc: Description of the partner
*
* Registers a device for USB Type-C Partner described in @desc.
*
* Returns handle to the partner on success or NULL on failure.
*/
struct typec_partner *typec_register_partner(struct typec_port *port,
struct typec_partner_desc *desc)
{
struct typec_partner *partner;
int ret;
partner = kzalloc(sizeof(*partner), GFP_KERNEL);
if (!partner)
return NULL;
partner->usb_pd = desc->usb_pd;
partner->accessory = desc->accessory;
if (desc->identity) {
/*
* Creating directory for the identity only if the driver is
* able to provide data to it.
*/
partner->dev.groups = usb_pd_id_groups;
partner->identity = desc->identity;
}
partner->dev.class = typec_class;
partner->dev.parent = &port->dev;
partner->dev.type = &typec_partner_dev_type;
dev_set_name(&partner->dev, "%s-partner", dev_name(&port->dev));
ret = device_register(&partner->dev);
if (ret) {
dev_err(&port->dev, "failed to register partner (%d)\n", ret);
put_device(&partner->dev);
return NULL;
}
return partner;
}
EXPORT_SYMBOL_GPL(typec_register_partner);
/**
* typec_unregister_partner - Unregister a USB Type-C Partner
* @partner: The partner to be unregistered
*
* Unregister device created with typec_register_partner().
*/
void typec_unregister_partner(struct typec_partner *partner)
{
if (partner)
device_unregister(&partner->dev);
}
EXPORT_SYMBOL_GPL(typec_unregister_partner);
/* ------------------------------------------------------------------------- */
/* Type-C Cable Plugs */
static void typec_plug_release(struct device *dev)
{
struct typec_plug *plug = to_typec_plug(dev);
kfree(plug);
}
static const struct device_type typec_plug_dev_type = {
.name = "typec_plug",
.release = typec_plug_release,
};
/**
* typec_plug_register_altmode - Register USB Type-C Cable Plug Alternate Mode
* @plug: USB Type-C Cable Plug that supports the alternate mode
* @desc: Description of the alternate mode
*
* This routine is used to register each alternate mode individually that @plug
* has listed in response to Discover SVIDs command. The modes for a SVID that
* the plug lists in response to Discover Modes command need to be listed in an
* array in @desc.
*
* Returns handle to the alternate mode on success or NULL on failure.
*/
struct typec_altmode *
typec_plug_register_altmode(struct typec_plug *plug,
struct typec_altmode_desc *desc)
{
return typec_register_altmode(&plug->dev, desc);
}
EXPORT_SYMBOL_GPL(typec_plug_register_altmode);
/**
* typec_register_plug - Register a USB Type-C Cable Plug
* @cable: USB Type-C Cable with the plug
* @desc: Description of the cable plug
*
* Registers a device for USB Type-C Cable Plug described in @desc. A USB Type-C
* Cable Plug represents a plug with electronics in it that can response to USB
* Power Delivery SOP Prime or SOP Double Prime packages.
*
* Returns handle to the cable plug on success or NULL on failure.
*/
struct typec_plug *typec_register_plug(struct typec_cable *cable,
struct typec_plug_desc *desc)
{
struct typec_plug *plug;
char name[8];
int ret;
plug = kzalloc(sizeof(*plug), GFP_KERNEL);
if (!plug)
return NULL;
sprintf(name, "plug%d", desc->index);
plug->index = desc->index;
plug->dev.class = typec_class;
plug->dev.parent = &cable->dev;
plug->dev.type = &typec_plug_dev_type;
dev_set_name(&plug->dev, "%s-%s", dev_name(cable->dev.parent), name);
ret = device_register(&plug->dev);
if (ret) {
dev_err(&cable->dev, "failed to register plug (%d)\n", ret);
put_device(&plug->dev);
return NULL;
}
return plug;
}
EXPORT_SYMBOL_GPL(typec_register_plug);
/**
* typec_unregister_plug - Unregister a USB Type-C Cable Plug
* @plug: The cable plug to be unregistered
*
* Unregister device created with typec_register_plug().
*/
void typec_unregister_plug(struct typec_plug *plug)
{
if (plug)
device_unregister(&plug->dev);
}
EXPORT_SYMBOL_GPL(typec_unregister_plug);
/* Type-C Cables */
static ssize_t
type_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct typec_cable *cable = to_typec_cable(dev);
return sprintf(buf, "%s\n", cable->active ? "active" : "passive");
}
static DEVICE_ATTR_RO(type);
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_type.attr,
&dev_attr_plug_type.attr,
NULL
};
ATTRIBUTE_GROUPS(typec_cable);
static void typec_cable_release(struct device *dev)
{
struct typec_cable *cable = to_typec_cable(dev);
kfree(cable);
}
static const struct device_type typec_cable_dev_type = {
.name = "typec_cable",
.groups = typec_cable_groups,
.release = typec_cable_release,
};
/**
* typec_cable_set_identity - Report result from Discover Identity command
* @cable: The cable updated identity values
*
* This routine is used to report that the result of Discover Identity USB power
* delivery command has become available.
*/
int typec_cable_set_identity(struct typec_cable *cable)
{
if (!cable->identity)
return -EINVAL;
typec_report_identity(&cable->dev);
return 0;
}
EXPORT_SYMBOL_GPL(typec_cable_set_identity);
/**
* typec_register_cable - Register a USB Type-C Cable
* @port: The USB Type-C Port the cable is connected to
* @desc: Description of the cable
*
* Registers a device for USB Type-C Cable described in @desc. The cable will be
* parent for the optional cable plug devises.
*
* Returns handle to the cable on success or NULL on failure.
*/
struct typec_cable *typec_register_cable(struct typec_port *port,
struct typec_cable_desc *desc)
{
struct typec_cable *cable;
int ret;
cable = kzalloc(sizeof(*cable), GFP_KERNEL);
if (!cable)
return NULL;
cable->type = desc->type;
cable->active = desc->active;
if (desc->identity) {
/*
* Creating directory for the identity only if the driver is
* able to provide data to it.
*/
cable->dev.groups = usb_pd_id_groups;
cable->identity = desc->identity;
}
cable->dev.class = typec_class;
cable->dev.parent = &port->dev;
cable->dev.type = &typec_cable_dev_type;
dev_set_name(&cable->dev, "%s-cable", dev_name(&port->dev));
ret = device_register(&cable->dev);
if (ret) {
dev_err(&port->dev, "failed to register cable (%d)\n", ret);
put_device(&cable->dev);
return NULL;
}
return cable;
}
EXPORT_SYMBOL_GPL(typec_register_cable);
/**
* typec_unregister_cable - Unregister a USB Type-C Cable
* @cable: The cable to be unregistered
*
* Unregister device created with typec_register_cable().
*/
void typec_unregister_cable(struct typec_cable *cable)
{
if (cable)
device_unregister(&cable->dev);
}
EXPORT_SYMBOL_GPL(typec_unregister_cable);
/* ------------------------------------------------------------------------- */
/* USB Type-C ports */
static const char * const typec_roles[] = {
[TYPEC_SINK] = "sink",
[TYPEC_SOURCE] = "source",
};
static const char * const typec_data_roles[] = {
[TYPEC_DEVICE] = "device",
[TYPEC_HOST] = "host",
};
static ssize_t
preferred_role_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t size)
{
struct typec_port *port = to_typec_port(dev);
int role;
int ret;
if (port->cap->type != TYPEC_PORT_DRP) {
dev_dbg(dev, "Preferred role only supported with DRP ports\n");
return -EOPNOTSUPP;
}
if (!port->cap->try_role) {
dev_dbg(dev, "Setting preferred role not supported\n");
return -EOPNOTSUPP;
}
role = sysfs_match_string(typec_roles, buf);
if (role < 0) {
if (sysfs_streq(buf, "none"))
role = TYPEC_NO_PREFERRED_ROLE;
else
return -EINVAL;
}
ret = port->cap->try_role(port->cap, role);
if (ret)
return ret;
port->prefer_role = role;
return size;
}
static ssize_t
preferred_role_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct typec_port *port = to_typec_port(dev);
if (port->cap->type != TYPEC_PORT_DRP)
return 0;
if (port->prefer_role < 0)
return 0;
return sprintf(buf, "%s\n", typec_roles[port->prefer_role]);
}
static DEVICE_ATTR_RW(preferred_role);
static ssize_t 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 data_role_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct typec_port *port = to_typec_port(dev);
if (port->cap->type == TYPEC_PORT_DRP)
return sprintf(buf, "%s\n", port->data_role == TYPEC_HOST ?
"[host] device" : "host [device]");
return sprintf(buf, "[%s]\n", typec_data_roles[port->data_role]);
}
static DEVICE_ATTR_RW(data_role);
static ssize_t power_role_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct typec_port *port = to_typec_port(dev);
int ret = size;
if (!port->cap->pd_revision) {
dev_dbg(dev, "USB Power Delivery not supported\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 power_role_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct typec_port *port = to_typec_port(dev);
if (port->cap->type == TYPEC_PORT_DRP)
return sprintf(buf, "%s\n", port->pwr_role == TYPEC_SOURCE ?
"[source] sink" : "source [sink]");
return sprintf(buf, "[%s]\n", typec_roles[port->pwr_role]);
}
static DEVICE_ATTR_RW(power_role);
static const char * const typec_pwr_opmodes[] = {
[TYPEC_PWR_MODE_USB] = "default",
[TYPEC_PWR_MODE_1_5A] = "1.5A",
[TYPEC_PWR_MODE_3_0A] = "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);
bool source;
int ret;
if (!port->cap->pd_revision) {
dev_dbg(dev, "VCONN swap depends on USB Power Delivery\n");
return -EOPNOTSUPP;
}
if (!port->cap->vconn_set) {
dev_dbg(dev, "VCONN swapping not supported\n");
return -EOPNOTSUPP;
}
ret = kstrtobool(buf, &source);
if (ret)
return ret;
ret = port->cap->vconn_set(port->cap, (enum typec_role)source);
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, "%s\n",
port->vconn_role == TYPEC_SOURCE ? "yes" : "no");
}
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;
for (i = 0; i < ARRAY_SIZE(port->cap->accessory); i++) {
if (port->cap->accessory[i])
ret += sprintf(buf + ret, "%s ",
typec_accessory_modes[port->cap->accessory[i]]);
}
if (!ret)
return sprintf(buf, "none\n");
buf[ret - 1] = '\n';
return ret;
}
static DEVICE_ATTR_RO(supported_accessory_modes);
static ssize_t usb_typec_revision_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct typec_port *port = to_typec_port(dev);
u16 rev = port->cap->revision;
return sprintf(buf, "%d.%d\n", (rev >> 8) & 0xff, (rev >> 4) & 0xf);
}
static DEVICE_ATTR_RO(usb_typec_revision);
static ssize_t usb_power_delivery_revision_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct typec_port *p = to_typec_port(dev);
return sprintf(buf, "%d\n", (p->cap->pd_revision >> 8) & 0xff);
}
static DEVICE_ATTR_RO(usb_power_delivery_revision);
static struct attribute *typec_attrs[] = {
&dev_attr_data_role.attr,
&dev_attr_power_operation_mode.attr,
&dev_attr_power_role.attr,
&dev_attr_preferred_role.attr,
&dev_attr_supported_accessory_modes.attr,
&dev_attr_usb_power_delivery_revision.attr,
&dev_attr_usb_typec_revision.attr,
&dev_attr_vconn_source.attr,
NULL,
};
ATTRIBUTE_GROUPS(typec);
static int typec_uevent(struct device *dev, struct kobj_uevent_env *env)
{
int ret;
ret = add_uevent_var(env, "TYPEC_PORT=%s", dev_name(dev));
if (ret)
dev_err(dev, "failed to add uevent TYPEC_PORT\n");
return ret;
}
static void typec_release(struct device *dev)
{
struct typec_port *port = to_typec_port(dev);
ida_simple_remove(&typec_index_ida, port->id);
kfree(port);
}
static const struct device_type typec_port_dev_type = {
.name = "typec_port",
.groups = typec_groups,
.uevent = typec_uevent,
.release = typec_release,
};
/* --------------------------------------- */
/* Driver callbacks to report role updates */
/**
* typec_set_data_role - Report data role change
* @port: The USB Type-C Port where the role was changed
* @role: The new data role
*
* This routine is used by the port drivers to report data role changes.
*/
void typec_set_data_role(struct typec_port *port, enum typec_data_role role)
{
if (port->data_role == role)
return;
port->data_role = role;
sysfs_notify(&port->dev.kobj, NULL, "data_role");
kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
}
EXPORT_SYMBOL_GPL(typec_set_data_role);
/**
* typec_set_pwr_role - Report power role change
* @port: The USB Type-C Port where the role was changed
* @role: The new data role
*
* This routine is used by the port drivers to report power role changes.
*/
void typec_set_pwr_role(struct typec_port *port, enum typec_role role)
{
if (port->pwr_role == role)
return;
port->pwr_role = role;
sysfs_notify(&port->dev.kobj, NULL, "power_role");
kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
}
EXPORT_SYMBOL_GPL(typec_set_pwr_role);
/**
* typec_set_pwr_role - Report VCONN source change
* @port: The USB Type-C Port which VCONN role changed
* @role: Source when @port is sourcing VCONN, or Sink when it's not
*
* This routine is used by the port drivers to report if the VCONN source is
* changes.
*/
void typec_set_vconn_role(struct typec_port *port, enum typec_role role)
{
if (port->vconn_role == role)
return;
port->vconn_role = role;
sysfs_notify(&port->dev.kobj, NULL, "vconn_source");
kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
}
EXPORT_SYMBOL_GPL(typec_set_vconn_role);
/**
* typec_set_pwr_opmode - Report changed power operation mode
* @port: The USB Type-C Port where the mode was changed
* @opmode: New power operation mode
*
* This routine is used by the port drivers to report changed power operation
* mode in @port. The modes are USB (default), 1.5A, 3.0A as defined in USB
* Type-C specification, and "USB Power Delivery" when the power levels are
* negotiated with methods defined in USB Power Delivery specification.
*/
void typec_set_pwr_opmode(struct typec_port *port,
enum typec_pwr_opmode opmode)
{
if (port->pwr_opmode == opmode)
return;
port->pwr_opmode = opmode;
sysfs_notify(&port->dev.kobj, NULL, "power_operation_mode");
kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
}
EXPORT_SYMBOL_GPL(typec_set_pwr_opmode);
/* --------------------------------------- */
/**
* typec_port_register_altmode - Register USB Type-C Port Alternate Mode
* @port: USB Type-C Port that supports the alternate mode
* @desc: Description of the alternate mode
*
* This routine is used to register an alternate mode that @port is capable of
* supporting.
*
* Returns handle to the alternate mode on success or NULL on failure.
*/
struct typec_altmode *
typec_port_register_altmode(struct typec_port *port,
struct typec_altmode_desc *desc)
{
return typec_register_altmode(&port->dev, desc);
}
EXPORT_SYMBOL_GPL(typec_port_register_altmode);
/**
* typec_register_port - Register a USB Type-C Port
* @parent: Parent device
* @cap: Description of the port
*
* Registers a device for USB Type-C Port described in @cap.
*
* Returns handle to the port on success or NULL on failure.
*/
struct typec_port *typec_register_port(struct device *parent,
const struct typec_capability *cap)
{
struct typec_port *port;
int role;
int ret;
int id;
port = kzalloc(sizeof(*port), GFP_KERNEL);
if (!port)
return NULL;
id = ida_simple_get(&typec_index_ida, 0, 0, GFP_KERNEL);
if (id < 0) {
kfree(port);
return NULL;
}
if (cap->type == TYPEC_PORT_DFP)
role = TYPEC_SOURCE;
else if (cap->type == TYPEC_PORT_UFP)
role = TYPEC_SINK;
else
role = cap->prefer_role;
if (role == TYPEC_SOURCE) {
port->data_role = TYPEC_HOST;
port->pwr_role = TYPEC_SOURCE;
port->vconn_role = TYPEC_SOURCE;
} else {
port->data_role = TYPEC_DEVICE;
port->pwr_role = TYPEC_SINK;
port->vconn_role = TYPEC_SINK;
}
port->id = id;
port->cap = cap;
port->prefer_role = cap->prefer_role;
port->dev.class = typec_class;
port->dev.parent = parent;
port->dev.fwnode = cap->fwnode;
port->dev.type = &typec_port_dev_type;
dev_set_name(&port->dev, "port%d", id);
ret = device_register(&port->dev);
if (ret) {
dev_err(parent, "failed to register port (%d)\n", ret);
put_device(&port->dev);
return NULL;
}
return port;
}
EXPORT_SYMBOL_GPL(typec_register_port);
/**
* typec_unregister_port - Unregister a USB Type-C Port
* @port: The port to be unregistered
*
* Unregister device created with typec_register_port().
*/
void typec_unregister_port(struct typec_port *port)
{
if (port)
device_unregister(&port->dev);
}
EXPORT_SYMBOL_GPL(typec_unregister_port);
static int __init typec_init(void)
{
typec_class = class_create(THIS_MODULE, "typec");
return PTR_ERR_OR_ZERO(typec_class);
}
subsys_initcall(typec_init);
static void __exit typec_exit(void)
{
class_destroy(typec_class);
ida_destroy(&typec_index_ida);
}
module_exit(typec_exit);
MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("USB Type-C Connector Class");
#ifndef __LINUX_USB_TYPEC_H
#define __LINUX_USB_TYPEC_H
#include <linux/types.h>
/* XXX: Once we have a header for USB Power Delivery, this belongs there */
#define ALTMODE_MAX_MODES 6
/* USB Type-C Specification releases */
#define USB_TYPEC_REV_1_0 0x100 /* 1.0 */
#define USB_TYPEC_REV_1_1 0x110 /* 1.1 */
#define USB_TYPEC_REV_1_2 0x120 /* 1.2 */
struct typec_altmode;
struct typec_partner;
struct typec_cable;
struct typec_plug;
struct typec_port;
struct fwnode_handle;
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,
};
#define TYPEC_MAX_ACCESSORY 3
/*
* struct usb_pd_identity - USB Power Delivery identity data
* @id_header: ID Header VDO
* @cert_stat: Cert Stat VDO
* @product: Product VDO
*
* USB power delivery Discover Identity command response data.
*
* REVISIT: This is USB Power Delivery specific information, so this structure
* probable belongs to USB Power Delivery header file once we have them.
*/
struct usb_pd_identity {
u32 id_header;
u32 cert_stat;
u32 product;
};
int typec_partner_set_identity(struct typec_partner *partner);
int typec_cable_set_identity(struct typec_cable *cable);
/*
* struct typec_mode_desc - Individual Mode of an Alternate Mode
* @index: Index of the Mode within the SVID
* @vdo: VDO returned by Discover Modes USB PD command
* @desc: Optional human readable description of the mode
* @roles: Only for ports. DRP if the mode is available in both roles
*
* Description of a mode of an Alternate Mode which a connector, cable plug or
* partner supports. Every mode will have it's own sysfs group. The details are
* the VDO returned by discover modes command, description for the mode and
* active flag telling has the mode being entered or not.
*/
struct typec_mode_desc {
int index;
u32 vdo;
char *desc;
/* Only used with ports */
enum typec_port_type roles;
};
/*
* struct typec_altmode_desc - USB Type-C Alternate Mode Descriptor
* @svid: Standard or Vendor ID
* @n_modes: Number of modes
* @modes: Array of modes supported by the Alternate Mode
*
* Representation of an Alternate Mode that has SVID assigned by USB-IF. The
* array of modes will list the modes of a particular SVID that are supported by
* a connector, partner of a cable plug.
*/
struct typec_altmode_desc {
u16 svid;
int n_modes;
struct typec_mode_desc modes[ALTMODE_MAX_MODES];
};
struct typec_altmode
*typec_partner_register_altmode(struct typec_partner *partner,
struct typec_altmode_desc *desc);
struct typec_altmode
*typec_plug_register_altmode(struct typec_plug *plug,
struct typec_altmode_desc *desc);
struct typec_altmode
*typec_port_register_altmode(struct typec_port *port,
struct typec_altmode_desc *desc);
void typec_unregister_altmode(struct typec_altmode *altmode);
struct typec_port *typec_altmode2port(struct typec_altmode *alt);
void typec_altmode_update_active(struct typec_altmode *alt, int mode,
bool active);
enum typec_plug_index {
TYPEC_PLUG_SOP_P,
TYPEC_PLUG_SOP_PP,
};
/*
* struct typec_plug_desc - USB Type-C Cable Plug Descriptor
* @index: SOP Prime for the plug connected to DFP and SOP Double Prime for the
* plug connected to UFP
*
* Represents USB Type-C Cable Plug.
*/
struct typec_plug_desc {
enum typec_plug_index index;
};
/*
* struct typec_cable_desc - USB Type-C Cable Descriptor
* @type: The plug type from USB PD Cable VDO
* @active: Is the cable active or passive
* @identity: Result of Discover Identity command
*
* Represents USB Type-C Cable attached to USB Type-C port.
*/
struct typec_cable_desc {
enum typec_plug_type type;
unsigned int active:1;
struct usb_pd_identity *identity;
};
/*
* struct typec_partner_desc - USB Type-C Partner Descriptor
* @usb_pd: USB Power Delivery support
* @accessory: Audio, Debug or none.
* @identity: Discover Identity command data
*
* Details about a partner that is attached to USB Type-C port. If @identity
* member exists when partner is registered, a directory named "identity" is
* created to sysfs for the partner device.
*/
struct typec_partner_desc {
unsigned int usb_pd:1;
enum typec_accessory accessory;
struct usb_pd_identity *identity;
};
/*
* struct typec_capability - USB Type-C Port Capabilities
* @role: DFP (Host-only), UFP (Device-only) or DRP (Dual Role)
* @revision: USB Type-C Specification release. Binary coded decimal
* @pd_revision: USB Power Delivery Specification revision if supported
* @prefer_role: Initial role preference
* @accessory: Supported Accessory Modes
* @fwnode: Optional fwnode of the port
* @try_role: Set data role preference for DRP port
* @dr_set: Set Data Role
* @pr_set: Set Power Role
* @vconn_set: Set VCONN Role
* @activate_mode: Enter/exit given Alternate Mode
*
* Static capabilities of a single USB Type-C port.
*/
struct typec_capability {
enum typec_port_type type;
u16 revision; /* 0120H = "1.2" */
u16 pd_revision; /* 0300H = "3.0" */
int prefer_role;
enum typec_accessory accessory[TYPEC_MAX_ACCESSORY];
struct fwnode_handle *fwnode;
int (*try_role)(const struct typec_capability *,
int role);
int (*dr_set)(const struct typec_capability *,
enum typec_data_role);
int (*pr_set)(const struct typec_capability *,
enum typec_role);
int (*vconn_set)(const struct typec_capability *,
enum typec_role);
int (*activate_mode)(const struct typec_capability *,
int mode, int activate);
};
/* Specific to try_role(). Indicates the user want's to clear the preference. */
#define TYPEC_NO_PREFERRED_ROLE (-1)
struct typec_port *typec_register_port(struct device *parent,
const struct typec_capability *cap);
void typec_unregister_port(struct typec_port *port);
struct typec_partner *typec_register_partner(struct typec_port *port,
struct typec_partner_desc *desc);
void typec_unregister_partner(struct typec_partner *partner);
struct typec_cable *typec_register_cable(struct typec_port *port,
struct typec_cable_desc *desc);
void typec_unregister_cable(struct typec_cable *cable);
struct typec_plug *typec_register_plug(struct typec_cable *cable,
struct typec_plug_desc *desc);
void typec_unregister_plug(struct typec_plug *plug);
void typec_set_data_role(struct typec_port *port, enum typec_data_role role);
void typec_set_pwr_role(struct typec_port *port, enum typec_role role);
void typec_set_vconn_role(struct typec_port *port, enum typec_role role);
void typec_set_pwr_opmode(struct typec_port *port, enum typec_pwr_opmode mode);
#endif /* __LINUX_USB_TYPEC_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment