linux-usb.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC PATCH 0/3] usb: typec: New attribute files for USB mode
@ 2019-12-30 15:28 Heikki Krogerus
  2019-12-30 15:28 ` [RFC PATCH 1/3] usb: typec: Add attribute file showing the USB mode of the port Heikki Krogerus
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Heikki Krogerus @ 2019-12-30 15:28 UTC (permalink / raw)
  To: Guenter Roeck; +Cc: Greg Kroah-Hartman, linux-usb

Hi Guenter,

These patches introduce the new sysfs attribute files that allow the
user to see and also control the USB mode. The last patch just adds
driver for the TBT3 alt mode. The series is made on top of:
https://lore.kernel.org/linux-usb/20191230142611.24921-1-heikki.krogerus@linux.intel.com/

What I'm proposing is that we introduce a new sysfs attribute file
named "usb_mode" for both the port and partner devices. The file
displays the supported USB modes (usb2, usb3, usb4) when read in with
both types of devices.

If the user writes to the port attribute file a mode, that mode will
be used as the value for the "USB mode" field in the Enter_USB message
next time there is a connection. Changing the active mode of the port
does not affect the current connection. However, if the user writes to
the partner attribute file a mode, Data Reset message is expected to
be send to the partner, and the new mode is to be used with the
Enter_USB message that follows (as defined in the latest USB Power
Delivery spec.).

So the attribute file is the same for both port and partner, but
writing to it has different behaviour.

The new attribute files should work, but I want to make sure that the
approach is acceptable?

thanks,

Heikki Krogerus (3):
  usb: typec: Add attribute file showing the USB mode of the port
  usb: typec: Add attribute file showing the USB mode of the partner
  usb: typec: Add driver for Thunderbolt 3 Alternate Mode

 Documentation/ABI/testing/sysfs-class-typec |  34 +++
 drivers/usb/typec/altmodes/Kconfig          |   9 +
 drivers/usb/typec/altmodes/Makefile         |   2 +
 drivers/usb/typec/altmodes/thunderbolt.c    | 309 ++++++++++++++++++++
 drivers/usb/typec/class.c                   | 133 ++++++++-
 include/linux/usb/typec.h                   |  11 +
 6 files changed, 497 insertions(+), 1 deletion(-)
 create mode 100644 drivers/usb/typec/altmodes/thunderbolt.c

-- 
2.24.1


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [RFC PATCH 1/3] usb: typec: Add attribute file showing the USB mode of the port
  2019-12-30 15:28 [RFC PATCH 0/3] usb: typec: New attribute files for USB mode Heikki Krogerus
@ 2019-12-30 15:28 ` Heikki Krogerus
  2019-12-30 15:28 ` [RFC PATCH 2/3] usb: typec: Add attribute file showing the USB mode of the partner Heikki Krogerus
  2019-12-30 15:28 ` [RFC PATCH 3/3] usb: typec: Add driver for Thunderbolt 3 Alternate Mode Heikki Krogerus
  2 siblings, 0 replies; 4+ messages in thread
From: Heikki Krogerus @ 2019-12-30 15:28 UTC (permalink / raw)
  To: Guenter Roeck; +Cc: Greg Kroah-Hartman, linux-usb

This attribute file, named "usb_mode", will show the
supported USB modes, which are USB 2.0, USB 3.2 and USB4.
These modes are defined in the latest USB Type-C (R2.0) and
USB Power Delivery (R3.0 V2.0) Specifications.

Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
---
 Documentation/ABI/testing/sysfs-class-typec | 12 ++++
 drivers/usb/typec/class.c                   | 61 +++++++++++++++++++++
 include/linux/usb/typec.h                   |  9 +++
 3 files changed, 82 insertions(+)

diff --git a/Documentation/ABI/testing/sysfs-class-typec b/Documentation/ABI/testing/sysfs-class-typec
index 0c2eb26fdc06..8df6f599c967 100644
--- a/Documentation/ABI/testing/sysfs-class-typec
+++ b/Documentation/ABI/testing/sysfs-class-typec
@@ -108,6 +108,18 @@ Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
 Description:
 		Revision number of the supported USB Type-C specification.
 
+What:		/sys/class/typec/<port>/usb_mode
+Date:		February 2020
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:	The supported USB Modes with the active one, that is to be used
+		next time with the Enter_USB message, in brackets. The active
+		mode can be changed by writing to the file when the connector
+		interface supports it.
+
+		Valid values:
+		- usb2 (USB 2.0)
+		- usb3 (USB 3.2)
+		- usb4 (USB4)
 
 USB Type-C partner devices (eg. /sys/class/typec/port0-partner/)
 
diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
index 70f3c5e9eb0c..07e4913f04c6 100644
--- a/drivers/usb/typec/class.c
+++ b/drivers/usb/typec/class.c
@@ -47,6 +47,7 @@ struct typec_port {
 	enum typec_pwr_opmode		pwr_opmode;
 	enum typec_port_type		port_type;
 	struct mutex			port_type_lock;
+	enum usb_mode			usb_mode;
 
 	enum typec_orientation		orientation;
 	struct typec_switch		*sw;
@@ -146,6 +147,54 @@ static void typec_report_identity(struct device *dev)
 	sysfs_notify(&dev->kobj, "identity", "product");
 }
 
+static const char * const usb_modes[] = {
+	[USB_MODE_NONE] = "none",
+	[USB_MODE_USB2] = "usb2",
+	[USB_MODE_USB3] = "usb3",
+	[USB_MODE_USB4] = "usb4"
+};
+
+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;
+	int len = 0;
+	int i;
+
+	for (i = USB_MODE_USB2; i < USB_MODE_USB4 + 1; i++) {
+		if (!(BIT(i - 1) & cap))
+			continue;
+
+		if (i == mode)
+			len += sprintf(buf + len, "[%s] ", usb_modes[i]);
+		else
+			len += sprintf(buf + len, "%s ", usb_modes[i]);
+	}
+
+	buf[len - 1] = '\n';
+	return len;
+}
+
+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);
+	int ret = 0;
+	int mode;
+
+	mode = sysfs_match_string(usb_modes, buf);
+	if (mode < 0)
+		return mode;
+
+	ret = port->ops->usb_mode_set(port, mode);
+	if (ret)
+		return ret;
+
+	return size;
+}
+static DEVICE_ATTR_RW(usb_mode);
+
 /* ------------------------------------------------------------------------- */
 /* Alternate Modes */
 
@@ -597,6 +646,7 @@ 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,
+	&dev_attr_usb_mode.attr,
 	NULL
 };
 ATTRIBUTE_GROUPS(typec_partner);
@@ -1273,6 +1323,7 @@ static struct attribute *typec_attrs[] = {
 	&dev_attr_usb_typec_revision.attr,
 	&dev_attr_vconn_source.attr,
 	&dev_attr_port_type.attr,
+	&dev_attr_usb_mode.attr,
 	NULL,
 };
 
@@ -1302,6 +1353,9 @@ static umode_t typec_attr_is_visible(struct kobject *kobj,
 			return 0;
 		if (port->cap->type != TYPEC_PORT_DRP)
 			return 0444;
+	} else if (attr == &dev_attr_usb_mode.attr) {
+		if (!port->ops || !port->ops->usb_mode_set)
+			return 0444;
 	}
 
 	return attr->mode;
@@ -1656,6 +1710,13 @@ struct typec_port *typec_register_port(struct device *parent,
 	port->port_type = cap->type;
 	port->prefer_role = cap->prefer_role;
 
+	if (cap->usb & USB_CAPABILITY_USB4)
+		port->usb_mode = USB_MODE_USB4;
+	else if (cap->usb & USB_CAPABILITY_USB3)
+		port->usb_mode = USB_MODE_USB3;
+	else if (cap->usb & USB_CAPABILITY_USB2)
+		port->usb_mode = USB_MODE_USB2;
+
 	device_initialize(&port->dev);
 	port->dev.class = typec_class;
 	port->dev.parent = parent;
diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
index 0acfbcd8bf04..1128c3b58618 100644
--- a/include/linux/usb/typec.h
+++ b/include/linux/usb/typec.h
@@ -72,6 +72,13 @@ enum typec_orientation {
 	TYPEC_ORIENTATION_REVERSE,
 };
 
+enum usb_mode {
+	USB_MODE_NONE,
+	USB_MODE_USB2,
+	USB_MODE_USB3,
+	USB_MODE_USB4
+};
+
 #define USB_CAPABILITY_USB2	BIT(0)
 #define USB_CAPABILITY_USB3	BIT(1)
 #define USB_CAPABILITY_USB4	BIT(2)
@@ -184,6 +191,7 @@ struct typec_partner_desc {
  * @pr_set: Set Power Role
  * @vconn_set: Source VCONN
  * @port_type_set: Set port type
+ * @usb_mode_set: Set the USB Mode to be used with Enter_USB message
  */
 struct typec_operations {
 	int (*try_role)(struct typec_port *port, int role);
@@ -192,6 +200,7 @@ struct typec_operations {
 	int (*vconn_set)(struct typec_port *port, enum typec_role role);
 	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);
 };
 
 /*
-- 
2.24.1


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [RFC PATCH 2/3] usb: typec: Add attribute file showing the USB mode of the partner
  2019-12-30 15:28 [RFC PATCH 0/3] usb: typec: New attribute files for USB mode Heikki Krogerus
  2019-12-30 15:28 ` [RFC PATCH 1/3] usb: typec: Add attribute file showing the USB mode of the port Heikki Krogerus
@ 2019-12-30 15:28 ` Heikki Krogerus
  2019-12-30 15:28 ` [RFC PATCH 3/3] usb: typec: Add driver for Thunderbolt 3 Alternate Mode Heikki Krogerus
  2 siblings, 0 replies; 4+ messages in thread
From: Heikki Krogerus @ 2019-12-30 15:28 UTC (permalink / raw)
  To: Guenter Roeck; +Cc: Greg Kroah-Hartman, linux-usb

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 --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);
 };
 
 /*
-- 
2.24.1


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [RFC PATCH 3/3] usb: typec: Add driver for Thunderbolt 3 Alternate Mode
  2019-12-30 15:28 [RFC PATCH 0/3] usb: typec: New attribute files for USB mode Heikki Krogerus
  2019-12-30 15:28 ` [RFC PATCH 1/3] usb: typec: Add attribute file showing the USB mode of the port Heikki Krogerus
  2019-12-30 15:28 ` [RFC PATCH 2/3] usb: typec: Add attribute file showing the USB mode of the partner Heikki Krogerus
@ 2019-12-30 15:28 ` Heikki Krogerus
  2 siblings, 0 replies; 4+ messages in thread
From: Heikki Krogerus @ 2019-12-30 15:28 UTC (permalink / raw)
  To: Guenter Roeck; +Cc: Greg Kroah-Hartman, linux-usb

Thunderbolt 3 Alternate Mode entry flow is described in
USB Type-C Specification Release 2.0.

Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
---
 drivers/usb/typec/altmodes/Kconfig       |   9 +
 drivers/usb/typec/altmodes/Makefile      |   2 +
 drivers/usb/typec/altmodes/thunderbolt.c | 309 +++++++++++++++++++++++
 3 files changed, 320 insertions(+)
 create mode 100644 drivers/usb/typec/altmodes/thunderbolt.c

diff --git a/drivers/usb/typec/altmodes/Kconfig b/drivers/usb/typec/altmodes/Kconfig
index 187690fd1a5b..e5c116067809 100644
--- a/drivers/usb/typec/altmodes/Kconfig
+++ b/drivers/usb/typec/altmodes/Kconfig
@@ -22,4 +22,13 @@ config TYPEC_NVIDIA_ALTMODE
 	  To compile this driver as a module, choose M here: the
 	  module will be called typec_displayport.
 
+config TYPEC_TBT_ALTMODE
+	tristate "Thunderbolt3 Alternate Mode driver"
+	help
+	  Select this option if you have Thunderbolt3 hardware on your
+	  system.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called typec_thunderbolt.
+
 endmenu
diff --git a/drivers/usb/typec/altmodes/Makefile b/drivers/usb/typec/altmodes/Makefile
index 45717548b396..508a68351bd2 100644
--- a/drivers/usb/typec/altmodes/Makefile
+++ b/drivers/usb/typec/altmodes/Makefile
@@ -4,3 +4,5 @@ obj-$(CONFIG_TYPEC_DP_ALTMODE)		+= typec_displayport.o
 typec_displayport-y			:= displayport.o
 obj-$(CONFIG_TYPEC_NVIDIA_ALTMODE)	+= typec_nvidia.o
 typec_nvidia-y				:= nvidia.o
+obj-$(CONFIG_TYPEC_TBT_ALTMODE)		+= typec_thunderbolt.o
+typec_thunderbolt-y			:= thunderbolt.o
diff --git a/drivers/usb/typec/altmodes/thunderbolt.c b/drivers/usb/typec/altmodes/thunderbolt.c
new file mode 100644
index 000000000000..abb6d2d0ffc9
--- /dev/null
+++ b/drivers/usb/typec/altmodes/thunderbolt.c
@@ -0,0 +1,309 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * USB Typec-C Thuderbolt3 Alternate Mode driver
+ *
+ * Copyright (C) 2019 Intel Corporation
+ * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/usb/pd_vdo.h>
+#include <linux/usb/typec_altmode.h>
+
+#define USB_TYPEC_VENDOR_INTEL		0x8087
+
+/* TBT3 Device Discover Mode VDO bits */
+#define TBT_MODE			BIT(0)
+#define TBT_ADAPTER(_vdo_)		(((_vdo_) & BIT(16)) >> 16)
+#define   TBT_ADAPTER_LEGACY		0
+#define   TBT_ADAPTER_TBT3		1
+#define TBT_INTEL_SPECIFIC_B0		BIT(26)
+#define TBT_VENDOR_SPECIFIC_B0		BIT(30)
+#define TBT_VENDOR_SPECIFIC_B1		BIT(31)
+
+/* TBT3 Cable Discover Mode VDO bits */
+#define TBT_CABLE_SPEED(_vdo_)		(((_vdo_) & GENMASK(18, 16)) >> 16)
+#define   TBT_CABLE_USB3_GEN1		1
+#define   TBT_CABLE_USB3_PASSIVE	2
+#define   TBT_CABLE_10_AND_20GBPS	3
+#define TBT_CABLE_ROUNDED		BIT(19)
+#define TBT_CABLE_OPTICAL		BIT(21)
+#define TBT_CABLE_RETIMER		BIT(22)
+#define TBT_CABLE_LINK_TRAINING		BIT(23)
+
+/* TBT3 Device Enter Mode VDO bits */
+#define TBT_ENTER_MODE_CABLE_SPEED(_s_)	(((_s_) & GENMASK(2, 0)) << 16)
+#define TBT_ENTER_MODE_ACTIVE_CABLE	BIT(24)
+
+enum tbt_state {
+	TBT_STATE_IDLE,
+	TBT_STATE_SOP_P_ENTER,
+	TBT_STATE_SOP_PP_ENTER,
+	TBT_STATE_ENTER,
+	TBT_STATE_EXIT,
+	TBT_STATE_SOP_PP_EXIT,
+	TBT_STATE_SOP_P_EXIT
+};
+
+struct tbt_altmode {
+	enum tbt_state state;
+	struct typec_cable *cable;
+	struct typec_altmode *alt;
+	struct typec_altmode *plug[2];
+
+	struct work_struct work;
+	struct mutex lock; /* device lock */
+};
+
+static int tbt_enter_mode(struct tbt_altmode *tbt)
+{
+	struct typec_altmode *plug = tbt->plug[TYPEC_PLUG_SOP_P];
+	u32 vdo;
+
+	vdo = tbt->alt->vdo & (TBT_VENDOR_SPECIFIC_B0 | TBT_VENDOR_SPECIFIC_B1);
+	vdo |= tbt->alt->vdo & TBT_INTEL_SPECIFIC_B0;
+	vdo |= TBT_MODE;
+
+	if (plug) {
+		if (typec_cable_is_active(tbt->cable))
+			vdo |= TBT_ENTER_MODE_ACTIVE_CABLE;
+
+		vdo |= TBT_ENTER_MODE_CABLE_SPEED(TBT_CABLE_SPEED(plug->vdo));
+		vdo |= plug->vdo & TBT_CABLE_ROUNDED;
+		vdo |= plug->vdo & TBT_CABLE_OPTICAL;
+		vdo |= plug->vdo & TBT_CABLE_RETIMER;
+		vdo |= plug->vdo & TBT_CABLE_LINK_TRAINING;
+	} else {
+		vdo |= TBT_ENTER_MODE_CABLE_SPEED(TBT_CABLE_USB3_PASSIVE);
+	}
+
+	return typec_altmode_enter(tbt->alt, &vdo);
+}
+
+static void tbt_altmode_work(struct work_struct *work)
+{
+	struct tbt_altmode *tbt = container_of(work, struct tbt_altmode, work);
+	int ret;
+
+	mutex_lock(&tbt->lock);
+
+	switch (tbt->state) {
+	case TBT_STATE_SOP_P_ENTER:
+		ret = typec_altmode_enter(tbt->plug[TYPEC_PLUG_SOP_P], NULL);
+		if (ret)
+			dev_dbg(&tbt->plug[TYPEC_PLUG_SOP_P]->dev,
+				"failed to enter mode (%d)\n", ret);
+		break;
+	case TBT_STATE_SOP_PP_ENTER:
+		ret = typec_altmode_enter(tbt->plug[TYPEC_PLUG_SOP_PP], NULL);
+		if (ret)
+			dev_dbg(&tbt->plug[TYPEC_PLUG_SOP_PP]->dev,
+				"failed to enter mode (%d)\n", ret);
+		break;
+	case TBT_STATE_ENTER:
+		ret = tbt_enter_mode(tbt);
+		if (ret)
+			dev_dbg(&tbt->alt->dev, "failed to enter mode (%d)\n",
+				ret);
+		break;
+	case TBT_STATE_EXIT:
+		typec_altmode_exit(tbt->alt);
+		break;
+	case TBT_STATE_SOP_PP_EXIT:
+		typec_altmode_exit(tbt->plug[TYPEC_PLUG_SOP_PP]);
+		break;
+	case TBT_STATE_SOP_P_EXIT:
+		typec_altmode_exit(tbt->plug[TYPEC_PLUG_SOP_P]);
+		break;
+	default:
+		break;
+	}
+
+	tbt->state = TBT_STATE_IDLE;
+
+	mutex_unlock(&tbt->lock);
+}
+
+static int tbt_altmode_vdm(struct typec_altmode *alt,
+			   const u32 hdr, const u32 *vdo, int count)
+{
+	struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt);
+	int cmd_type = PD_VDO_CMDT(hdr);
+	int cmd = PD_VDO_CMD(hdr);
+
+	mutex_lock(&tbt->lock);
+
+	if (tbt->state != TBT_STATE_IDLE) {
+		mutex_unlock(&tbt->lock);
+		return -EBUSY;
+	}
+
+	switch (cmd_type) {
+	case CMDT_RSP_ACK:
+		switch (cmd) {
+		case CMD_ENTER_MODE:
+			/*
+			 * Following the order describeded in USB Type-C Spec
+			 * R2.0 Section 6.7.3.
+			 */
+			if (alt == tbt->plug[TYPEC_PLUG_SOP_P]) {
+				if (tbt->plug[TYPEC_PLUG_SOP_PP])
+					tbt->state = TBT_STATE_SOP_PP_ENTER;
+				else
+					tbt->state = TBT_STATE_ENTER;
+			} else if (alt == tbt->plug[TYPEC_PLUG_SOP_PP]) {
+				tbt->state = TBT_STATE_ENTER;
+			} else {
+				typec_altmode_notify(alt, TYPEC_STATE_MODAL,
+						     NULL);
+			}
+			break;
+		case CMD_EXIT_MODE:
+			if (alt == tbt->alt) {
+				if (tbt->plug[TYPEC_PLUG_SOP_PP])
+					tbt->state = TBT_STATE_SOP_PP_EXIT;
+				else if (tbt->plug[TYPEC_PLUG_SOP_P])
+					tbt->state = TBT_STATE_SOP_P_EXIT;
+			} else if (alt == tbt->plug[TYPEC_PLUG_SOP_PP]) {
+				tbt->state = TBT_STATE_SOP_P_EXIT;
+			}
+			break;
+		}
+		break;
+	case CMDT_RSP_NAK:
+		switch (cmd) {
+		case CMD_ENTER_MODE:
+			dev_warn(&alt->dev, "Enter Mode refused\n");
+			break;
+		default:
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+
+	if (tbt->state != TBT_STATE_IDLE)
+		schedule_work(&tbt->work);
+
+	mutex_unlock(&tbt->lock);
+
+	return 0;
+}
+
+static int tbt_altmode_activate(struct typec_altmode *alt, int activate)
+{
+	struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt);
+	int ret;
+
+	mutex_lock(&tbt->lock);
+
+	/* Preventing the user space from entering/exiting the cable alt mode */
+	if (alt != tbt->alt)
+		ret = -EPERM;
+	else if (activate)
+		ret = tbt_enter_mode(tbt);
+	else
+		ret = typec_altmode_exit(alt);
+
+	mutex_unlock(&tbt->lock);
+
+	return ret;
+}
+
+static const struct typec_altmode_ops tbt_altmode_ops = {
+	.vdm		= tbt_altmode_vdm,
+	.activate	= tbt_altmode_activate
+};
+
+static int tbt_altmode_probe(struct typec_altmode *alt)
+{
+	struct typec_altmode *plug;
+	struct tbt_altmode *tbt;
+	int err;
+	int i;
+
+	tbt = devm_kzalloc(&alt->dev, sizeof(*tbt), GFP_KERNEL);
+	if (!tbt)
+		return -ENOMEM;
+
+	/* Thundebolt 3 requires a cable with eMarker */
+	tbt->cable = typec_cable_get(typec_altmode2port(alt));
+	if (!tbt->cable)
+		return -ENODEV;
+
+	for (i = 0; i < TYPEC_PLUG_SOP_PP + 1; i++) {
+		plug = typec_altmode_get_plug(alt, i);
+		if (IS_ERR(plug)) {
+			err = PTR_ERR(plug);
+			goto err_put_plugs;
+		}
+
+		if (!plug || plug->svid != USB_TYPEC_VENDOR_INTEL)
+			break;
+
+		plug->desc = "Thunderbolt3";
+		plug->ops = &tbt_altmode_ops;
+		typec_altmode_set_drvdata(plug, tbt);
+
+		tbt->plug[i] = plug;
+	}
+
+	INIT_WORK(&tbt->work, tbt_altmode_work);
+	mutex_init(&tbt->lock);
+	tbt->alt = alt;
+
+	alt->desc = "Thunderbolt3";
+	alt->ops = &tbt_altmode_ops;
+
+	typec_altmode_set_drvdata(alt, tbt);
+
+	if (tbt->plug[TYPEC_PLUG_SOP_PP])
+		tbt->state = TBT_STATE_SOP_PP_ENTER;
+	else if (tbt->plug[TYPEC_PLUG_SOP_P])
+		tbt->state = TBT_STATE_SOP_P_ENTER;
+	else
+		tbt->state = TBT_STATE_ENTER;
+	schedule_work(&tbt->work);
+
+	return 0;
+
+err_put_plugs:
+	typec_altmode_put_plug(tbt->plug[TYPEC_PLUG_SOP_PP]);
+	typec_altmode_put_plug(tbt->plug[TYPEC_PLUG_SOP_P]);
+	typec_cable_put(tbt->cable);
+
+	return err;
+}
+
+static void tbt_altmode_remove(struct typec_altmode *alt)
+{
+	struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt);
+
+	typec_altmode_put_plug(tbt->plug[TYPEC_PLUG_SOP_PP]);
+	typec_altmode_put_plug(tbt->plug[TYPEC_PLUG_SOP_P]);
+	typec_cable_put(tbt->cable);
+}
+
+static const struct typec_device_id tbt_typec_id[] = {
+	{ USB_TYPEC_VENDOR_INTEL, TYPEC_ANY_MODE },
+	{ }
+};
+MODULE_DEVICE_TABLE(typec, tbt_typec_id);
+
+static struct typec_altmode_driver tbt_altmode_driver = {
+	.id_table = tbt_typec_id,
+	.probe = tbt_altmode_probe,
+	.remove = tbt_altmode_remove,
+	.driver = {
+		.name = "typec-thunderbolt",
+		.owner = THIS_MODULE,
+	}
+};
+module_typec_altmode_driver(tbt_altmode_driver);
+
+MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Thunderbolt3 USB Type-C Alternate Mode");
-- 
2.24.1


^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2019-12-30 15:29 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2019-12-30 15:28 [RFC PATCH 0/3] usb: typec: New attribute files for USB mode Heikki Krogerus
2019-12-30 15:28 ` [RFC PATCH 1/3] usb: typec: Add attribute file showing the USB mode of the port Heikki Krogerus
2019-12-30 15:28 ` [RFC PATCH 2/3] usb: typec: Add attribute file showing the USB mode of the partner Heikki Krogerus
2019-12-30 15:28 ` [RFC PATCH 3/3] usb: typec: Add driver for Thunderbolt 3 Alternate Mode Heikki Krogerus

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).