Exactly the same attribute that we have for the port. With partners this attribute will get the information from the Discover Identity Command response. Signed-off-by: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx> --- Documentation/ABI/testing/sysfs-class-typec | 22 ++++++ drivers/usb/typec/class.c | 80 +++++++++++++++++++-- include/linux/usb/typec.h | 2 + 3 files changed, 99 insertions(+), 5 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-typec b/Documentation/ABI/testing/sysfs-class-typec index 8df6f599c967..f13c2b30fb3d 100644 --- a/Documentation/ABI/testing/sysfs-class-typec +++ b/Documentation/ABI/testing/sysfs-class-typec @@ -116,6 +116,12 @@ Description: The supported USB Modes with the active one, that is to be used mode can be changed by writing to the file when the connector interface supports it. + Note. This attribute file can not be used for resetting the mode + after the connection has been established. The mode can be reset + after connection by writing to the attribute file with the same + name ("usb_mode") of the partner device (this is the port device + that has the partner attached to). + Valid values: - usb2 (USB 2.0) - usb3 (USB 3.2) @@ -173,6 +179,22 @@ Description: will show 0 until Discover Identity command result becomes available. The value can be polled. +What: /sys/class/typec/<port>-partner/usb_mode +Date: February 2020 +Contact: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx> +Description: The USB Modes that the partner device supports. This information + requires the response from Discover Identity command, and will + therefore not always be available (as some firmware interfaces + do not share the information with the operating system). The + currently used mode can be changed by writing to this file when + the port driver is able to send Data Reset message to the + partner. + + Valid values: + - usb2 (USB 2.0) + - usb3 (USB 3.2) + - usb4 (USB4) + USB Type-C cable devices (eg. /sys/class/typec/port0-cable/) diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 07e4913f04c6..d318eee3b7ef 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -11,6 +11,7 @@ #include <linux/mutex.h> #include <linux/property.h> #include <linux/slab.h> +#include <linux/usb/pd_vdo.h> #include "bus.h" @@ -30,6 +31,7 @@ struct typec_cable { struct typec_partner { struct device dev; unsigned int usb_pd:1; + enum usb_mode usb_mode; struct usb_pd_identity *identity; enum typec_accessory accessory; struct ida mode_ids; @@ -154,14 +156,45 @@ static const char * const usb_modes[] = { [USB_MODE_USB4] = "usb4" }; +static u8 typec_partner_mode(struct typec_partner *partner) +{ + struct typec_port *port = to_typec_port(partner->dev.parent); + struct usb_pd_identity *id = partner->identity; + u32 dev_cap; + u8 cap = 0; + + if (port->data_role == TYPEC_HOST) { + dev_cap = PD_VDO1_UFP_DEVCAP(id->vdo[0]); + + if (dev_cap & (DEV_USB2_CAPABLE | DEV_USB2_BILLBOARD)) + cap |= USB_CAPABILITY_USB2; + if (dev_cap & DEV_USB3_CAPABLE) + cap |= USB_CAPABILITY_USB3; + if (dev_cap & DEV_USB4_CAPABLE) + cap |= USB_CAPABILITY_USB4; + } else { + cap = PD_VDO_DFP_HOSTCAP(id->vdo[0]); + } + + return cap; +} + static ssize_t usb_mode_show(struct device *dev, struct device_attribute *attr, char *buf) { - enum usb_mode mode = to_typec_port(dev)->usb_mode; - u8 cap = to_typec_port(dev)->cap->usb; + enum usb_mode mode = 0; int len = 0; + u8 cap = 0; int i; + if (is_typec_port(dev)) { + cap = to_typec_port(dev)->cap->usb; + mode = to_typec_port(dev)->usb_mode; + } else if (is_typec_partner(dev)) { + cap = typec_partner_mode(to_typec_partner(dev)); + mode = to_typec_partner(dev)->usb_mode; + } + for (i = USB_MODE_USB2; i < USB_MODE_USB4 + 1; i++) { if (!(BIT(i - 1) & cap)) continue; @@ -179,7 +212,7 @@ usb_mode_show(struct device *dev, struct device_attribute *attr, char *buf) static ssize_t usb_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { - struct typec_port *port = to_typec_port(dev); + struct typec_port *port; int ret = 0; int mode; @@ -187,7 +220,19 @@ static ssize_t usb_mode_store(struct device *dev, struct device_attribute *attr, if (mode < 0) return mode; - ret = port->ops->usb_mode_set(port, mode); + if (is_typec_port(dev)) { + port = to_typec_port(dev); + ret = port->ops->usb_mode_set(port, mode); + } else if (is_typec_partner(dev)) { + port = to_typec_port(dev->parent); + + /* Checking does the port support the mode */ + if (mode && !(BIT(mode - 1) & port->cap->usb)) + return -EINVAL; + + ret = port->ops->data_reset(port, mode); + } + if (ret) return ret; @@ -649,7 +694,32 @@ static struct attribute *typec_partner_attrs[] = { &dev_attr_usb_mode.attr, NULL }; -ATTRIBUTE_GROUPS(typec_partner); + +static umode_t typec_partner_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct typec_partner *partner = to_typec_partner(kobj_to_dev(kobj)); + struct typec_port *port = to_typec_port(partner->dev.parent); + + if (attr == &dev_attr_usb_mode.attr) { + if (!partner->identity) + return 0; + if (!port->ops || !port->ops->data_reset) + return 0444; + } + + return attr->mode; +} + +static struct attribute_group typec_partner_group = { + .is_visible = typec_partner_attr_is_visible, + .attrs = typec_partner_attrs +}; + +static const struct attribute_group *typec_partner_groups[] = { + &typec_partner_group, + NULL +}; static void typec_partner_release(struct device *dev) { diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h index 1128c3b58618..e548e4d21908 100644 --- a/include/linux/usb/typec.h +++ b/include/linux/usb/typec.h @@ -192,6 +192,7 @@ struct typec_partner_desc { * @vconn_set: Source VCONN * @port_type_set: Set port type * @usb_mode_set: Set the USB Mode to be used with Enter_USB message + * @data_reset: Set new USB mode by using the Data Reset message */ struct typec_operations { int (*try_role)(struct typec_port *port, int role); @@ -201,6 +202,7 @@ struct typec_operations { int (*port_type_set)(struct typec_port *port, enum typec_port_type type); int (*usb_mode_set)(struct typec_port *port, enum usb_mode mode); + int (*data_reset)(struct typec_port *port, enum usb_mode mode); }; /* -- 2.24.1