diff mbox series

[04/10] usb: typec: Expose alternate mode priorities via sysfs

Message ID 20250616133147.1835939-5-akuchynski@chromium.org
State New
Headers show
Series USB Type-C mode selection | expand

Commit Message

Andrei Kuchynski June 16, 2025, 1:31 p.m. UTC
This sysfs attribute specifies the preferred order for enabling
DisplayPort alt-mode, Thunderbolt alt-mode, and USB4 mode.

Signed-off-by: Andrei Kuchynski <akuchynski@chromium.org>
---
 Documentation/ABI/testing/sysfs-class-typec | 17 ++++
 drivers/usb/typec/Makefile                  |  2 +-
 drivers/usb/typec/class.c                   | 26 ++++++
 drivers/usb/typec/class.h                   |  2 +
 drivers/usb/typec/mode_selection.c          | 93 +++++++++++++++++++++
 drivers/usb/typec/mode_selection.h          |  5 ++
 include/linux/usb/typec_altmode.h           |  7 ++
 7 files changed, 151 insertions(+), 1 deletion(-)
 create mode 100644 drivers/usb/typec/mode_selection.c
 create mode 100644 drivers/usb/typec/mode_selection.h
diff mbox series

Patch

diff --git a/Documentation/ABI/testing/sysfs-class-typec b/Documentation/ABI/testing/sysfs-class-typec
index 38e101c17a00..46eee82042ab 100644
--- a/Documentation/ABI/testing/sysfs-class-typec
+++ b/Documentation/ABI/testing/sysfs-class-typec
@@ -162,6 +162,23 @@  Description:	Lists the supported USB Modes. The default USB mode that is used
 		- usb3 (USB 3.2)
 		- usb4 (USB4)
 
+What:		/sys/class/typec/<port>/altmode_priorities
+Date:		June 2025
+Contact:	Andrei Kuchynski <akuchynski@chromium.org>
+Description:	Lists the alternate modes supported by the port and their
+		priorities. The priority setting determines the order in which
+		Displayport alt-mode, Thunderbolt alt-mode and USB4 mode will be
+		activated, indicating the preferred selection sequence. A value of -1
+		disables automatic entry into a specific mode, while lower numbers
+		indicate higher priority. The default priorities can be modified by
+		assigning new values. Modes without explicitly set values default to -1,
+		effectively disabling them.
+
+		Example values:
+		- "USB4=0 TBT=1 DP=2"
+		- "USB4=-1 TBT=0"
+		- "DP=-1 USB4=-1 TBT=-1"
+
 USB Type-C partner devices (eg. /sys/class/typec/port0-partner/)
 
 What:		/sys/class/typec/<port>-partner/accessory_mode
diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
index 7a368fea61bc..8a6a1c663eb6 100644
--- a/drivers/usb/typec/Makefile
+++ b/drivers/usb/typec/Makefile
@@ -1,6 +1,6 @@ 
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_TYPEC)		+= typec.o
-typec-y				:= class.o mux.o bus.o pd.o retimer.o
+typec-y				:= class.o mux.o bus.o pd.o retimer.o mode_selection.o
 typec-$(CONFIG_ACPI)		+= port-mapper.o
 obj-$(CONFIG_TYPEC)		+= altmodes/
 obj-$(CONFIG_TYPEC_TCPM)	+= tcpm/
diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
index a72325ff099a..726fc0411c44 100644
--- a/drivers/usb/typec/class.c
+++ b/drivers/usb/typec/class.c
@@ -19,6 +19,7 @@ 
 #include "bus.h"
 #include "class.h"
 #include "pd.h"
+#include "mode_selection.h"
 
 static DEFINE_IDA(typec_index_ida);
 
@@ -1942,6 +1943,25 @@  static ssize_t orientation_show(struct device *dev,
 }
 static DEVICE_ATTR_RO(orientation);
 
+static ssize_t altmode_priorities_store(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf, size_t size)
+{
+	struct typec_port *port = to_typec_port(dev);
+	int ret = typec_mode_priorities_set(port, buf);
+
+	return ret ? : size;
+}
+
+static ssize_t altmode_priorities_show(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct typec_port *port = to_typec_port(dev);
+
+	return typec_mode_priorities_get(port, buf);
+}
+static DEVICE_ATTR_RW(altmode_priorities);
+
 static struct attribute *typec_attrs[] = {
 	&dev_attr_data_role.attr,
 	&dev_attr_power_operation_mode.attr,
@@ -1954,6 +1974,7 @@  static struct attribute *typec_attrs[] = {
 	&dev_attr_port_type.attr,
 	&dev_attr_orientation.attr,
 	&dev_attr_usb_capability.attr,
+	&dev_attr_altmode_priorities.attr,
 	NULL,
 };
 
@@ -1992,6 +2013,9 @@  static umode_t typec_attr_is_visible(struct kobject *kobj,
 			return 0;
 		if (!port->ops || !port->ops->default_usb_mode_set)
 			return 0444;
+	} else if (attr == &dev_attr_altmode_priorities.attr) {
+		if (!port->alt_mode_override)
+			return 0;
 	}
 
 	return attr->mode;
@@ -2652,6 +2676,8 @@  struct typec_port *typec_register_port(struct device *parent,
 	else if (cap->usb_capability & USB_CAPABILITY_USB2)
 		port->usb_mode = USB_MODE_USB2;
 
+	typec_mode_priorities_set(port, NULL);
+
 	device_initialize(&port->dev);
 	port->dev.class = &typec_class;
 	port->dev.parent = parent;
diff --git a/drivers/usb/typec/class.h b/drivers/usb/typec/class.h
index f05d9201c233..dffe5ef03bc6 100644
--- a/drivers/usb/typec/class.h
+++ b/drivers/usb/typec/class.h
@@ -5,6 +5,7 @@ 
 
 #include <linux/device.h>
 #include <linux/usb/typec.h>
+#include <linux/usb/typec_altmode.h>
 
 struct typec_mux;
 struct typec_switch;
@@ -82,6 +83,7 @@  struct typec_port {
 	struct device			*usb3_dev;
 
 	bool				alt_mode_override;
+	int				altmode_priorities[TYPEC_ALTMODE_MAX];
 };
 
 #define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev)
diff --git a/drivers/usb/typec/mode_selection.c b/drivers/usb/typec/mode_selection.c
new file mode 100644
index 000000000000..d984c79eef45
--- /dev/null
+++ b/drivers/usb/typec/mode_selection.c
@@ -0,0 +1,93 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2025 Google LLC.
+ */
+
+#include <linux/usb/typec_altmode.h>
+#include <linux/vmalloc.h>
+#include "class.h"
+
+#define MODE_PRIORITY_DISABLED -1
+
+static const char * const altmode_names[] = {
+	[TYPEC_ALTMODE_DP] = "DP",
+	[TYPEC_ALTMODE_TBT] = "TBT",
+	[TYPEC_ALTMODE_USB4] = "USB4",
+};
+static const char * const default_priorities = "USB4=0 TBT=1 DP=2";
+
+/* -------------------------------------------------------------------------- */
+/* port 'altmode_priorities' attribute */
+
+int typec_mode_priorities_set(struct typec_port *port,
+		const char *user_priorities)
+{
+	int priorities[TYPEC_ALTMODE_MAX];
+	const char *str_priority = user_priorities ? : default_priorities;
+	char *buf, *buf_free;
+	int ret = -EINVAL;
+	char *str_name;
+	int i;
+
+	buf = vmalloc(strlen(str_priority) + 1);
+	if (!buf)
+		return -ENOMEM;
+	strscpy(buf, str_priority, strlen(str_priority) + 1);
+	buf_free = buf;
+
+	for (i = 0; i < TYPEC_ALTMODE_MAX; i++)
+		priorities[i] = MODE_PRIORITY_DISABLED;
+
+	while ((str_name = strsep(&buf, " "))) {
+		char *str_value = strchr(str_name, '=');
+		int value;
+		int mode;
+
+		ret = -EINVAL;
+		if (!str_value)
+			goto parse_exit;
+		*str_value++ = '\0';
+
+		if (kstrtoint(str_value, 10, &value) ||
+			value < MODE_PRIORITY_DISABLED)
+			goto parse_exit;
+
+		if (value > MODE_PRIORITY_DISABLED) {
+			for (i = 0; i < TYPEC_ALTMODE_MAX; i++)
+				if (value == priorities[i])
+					goto parse_exit;
+		}
+
+		for (mode = 0; mode < TYPEC_ALTMODE_MAX &&
+			strcmp(str_name, altmode_names[mode]);)
+			mode++;
+		if (mode == TYPEC_ALTMODE_MAX ||
+			priorities[mode] != MODE_PRIORITY_DISABLED)
+			goto parse_exit;
+
+		priorities[mode] = value;
+		ret = 0;
+	}
+
+	for (i = 0; i < TYPEC_ALTMODE_MAX; i++)
+		port->altmode_priorities[i] = priorities[i];
+
+parse_exit:
+	vfree(buf_free);
+
+	return ret;
+}
+
+int typec_mode_priorities_get(struct typec_port *port, char *buf)
+{
+	ssize_t count = 0;
+	int i;
+
+	for (i = 0; i < TYPEC_ALTMODE_MAX; i++) {
+		if (i != TYPEC_ALTMODE_USB4 ||
+				port->cap->usb_capability & USB_CAPABILITY_USB4)
+			count += sysfs_emit_at(buf, count, "%s=%d ",
+				altmode_names[i], port->altmode_priorities[i]);
+	}
+	return count + sysfs_emit_at(buf, count, "\n");
+}
diff --git a/drivers/usb/typec/mode_selection.h b/drivers/usb/typec/mode_selection.h
new file mode 100644
index 000000000000..643f04f48343
--- /dev/null
+++ b/drivers/usb/typec/mode_selection.h
@@ -0,0 +1,5 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+
+int typec_mode_priorities_set(struct typec_port *port,
+		const char *user_priorities);
+int typec_mode_priorities_get(struct typec_port *port, char *buf);
diff --git a/include/linux/usb/typec_altmode.h b/include/linux/usb/typec_altmode.h
index b3c0866ea70f..7ca2040ee1e4 100644
--- a/include/linux/usb/typec_altmode.h
+++ b/include/linux/usb/typec_altmode.h
@@ -145,6 +145,13 @@  enum {
 
 #define TYPEC_MODAL_STATE(_state_)	((_state_) + TYPEC_STATE_MODAL)
 
+enum {
+	TYPEC_ALTMODE_DP = 0,
+	TYPEC_ALTMODE_TBT,
+	TYPEC_ALTMODE_USB4,
+	TYPEC_ALTMODE_MAX,
+};
+
 struct typec_altmode *typec_altmode_get_plug(struct typec_altmode *altmode,
 					     enum typec_plug_index index);
 void typec_altmode_put_plug(struct typec_altmode *plug);