diff mbox series

[RFC,2/3] usb: typec: Add attribute file showing the USB mode of the partner

Message ID 20191230152857.43917-3-heikki.krogerus@linux.intel.com (mailing list archive)
State New, archived
Headers show
Series usb: typec: New attribute files for USB mode | expand

Commit Message

Heikki Krogerus Dec. 30, 2019, 3:28 p.m. UTC
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@linux.intel.com>
---
 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 mbox series

Patch

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@linux.intel.com>
+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);
 };
 
 /*