[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250630141239.3174390-5-akuchynski@chromium.org>
Date: Mon, 30 Jun 2025 14:12:33 +0000
From: Andrei Kuchynski <akuchynski@...omium.org>
To: Heikki Krogerus <heikki.krogerus@...ux.intel.com>,
Abhishek Pandit-Subedi <abhishekpandit@...omium.org>,
Benson Leung <bleung@...omium.org>,
Jameson Thies <jthies@...gle.com>,
Tzung-Bi Shih <tzungbi@...nel.org>,
linux-usb@...r.kernel.org,
chrome-platform@...ts.linux.dev
Cc: Guenter Roeck <groeck@...omium.org>,
Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
Dmitry Baryshkov <lumag@...nel.org>,
"Christian A. Ehrhardt" <lk@...e.de>,
linux-kernel@...r.kernel.org,
Andrei Kuchynski <akuchynski@...omium.org>
Subject: [PATCH v2 04/10] usb: typec: Expose mode priorities via sysfs
This sysfs attribute specifies the preferred order for enabling
DisplayPort, Thunderbolt alternate modes, and USB4 mode.
Signed-off-by: Andrei Kuchynski <akuchynski@...omium.org>
---
Documentation/ABI/testing/sysfs-class-typec | 16 +++
drivers/usb/typec/Makefile | 2 +-
drivers/usb/typec/class.c | 28 ++++-
drivers/usb/typec/class.h | 4 +
drivers/usb/typec/mode_selection.c | 116 ++++++++++++++++++++
drivers/usb/typec/mode_selection.h | 19 ++++
include/linux/usb/typec_altmode.h | 7 ++
7 files changed, 190 insertions(+), 2 deletions(-)
create mode 100644 drivers/usb/typec/mode_selection.c
create mode 100644 drivers/usb/typec/mode_selection.h
diff --git a/Documentation/ABI/testing/sysfs-class-typec b/Documentation/ABI/testing/sysfs-class-typec
index 38e101c17a00..ff3296ee8e1c 100644
--- a/Documentation/ABI/testing/sysfs-class-typec
+++ b/Documentation/ABI/testing/sysfs-class-typec
@@ -162,6 +162,22 @@ Description: Lists the supported USB Modes. The default USB mode that is used
- usb3 (USB 3.2)
- usb4 (USB4)
+What: /sys/class/typec/<port>/mode_priorities
+Date: June 2025
+Contact: Andrei Kuchynski <akuchynski@...omium.org>
+Description: Lists the modes supported by the port, ordered by their
+ activation priority. It defines the preferred sequence for activating
+ modes such as Displayport alt-mode, Thunderbolt alt-mode and USB4 mode.
+ The default order can be modified by writing a new sequence to this
+ attribute. Any modes omitted from a user-provided list will be
+ automatically placed at the end of the list.
+
+ Example values:
+ - "USB4 TBT DP": default priority order
+ - "USB4 DP TBT": modified priority order after writing "USB4 DP TBT" or
+ "USB4 DP"
+ - "DP": the port only supports Displayport alt-mode
+
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..93eadbcdd4c0 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);
@@ -540,7 +541,7 @@ static void typec_altmode_release(struct device *dev)
}
const struct device_type typec_altmode_dev_type = {
- .name = "typec_alternate_mode",
+ .name = ALTERNATE_MODE_DEVICE_TYPE_NAME,
.groups = typec_altmode_groups,
.release = typec_altmode_release,
};
@@ -1942,6 +1943,25 @@ static ssize_t orientation_show(struct device *dev,
}
static DEVICE_ATTR_RO(orientation);
+static ssize_t mode_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 mode_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(mode_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_mode_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_mode_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, "");
+
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..28b3c19a0632 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 mode_priority_list[TYPEC_MODE_MAX];
};
#define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev)
@@ -111,4 +113,6 @@ static inline int typec_link_ports(struct typec_port *connector) { return 0; }
static inline void typec_unlink_ports(struct typec_port *connector) { }
#endif
+#define ALTERNATE_MODE_DEVICE_TYPE_NAME "typec_alternate_mode"
+
#endif /* __USB_TYPEC_CLASS__ */
diff --git a/drivers/usb/typec/mode_selection.c b/drivers/usb/typec/mode_selection.c
new file mode 100644
index 000000000000..cb7ddf679037
--- /dev/null
+++ b/drivers/usb/typec/mode_selection.c
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2025 Google LLC.
+ */
+
+#include <linux/usb/typec_altmode.h>
+#include <linux/vmalloc.h>
+#include "mode_selection.h"
+#include "class.h"
+
+static const char * const mode_names[] = {
+ [TYPEC_DP_ALTMODE] = "DP",
+ [TYPEC_TBT_ALTMODE] = "TBT",
+ [TYPEC_USB4_MODE] = "USB4",
+};
+static const char * const default_priorities = "USB4 TBT DP";
+
+/* -------------------------------------------------------------------------- */
+/* port 'mode_priorities' attribute */
+static int typec_mode_parse_priority_string(const char *str, int *list)
+{
+ const bool user_settings = list[0] == TYPEC_MODE_MAX;
+ char *buf, *ptr;
+ char *token;
+ int ret = 0;
+
+ buf = vmalloc(strlen(str) + 1);
+ if (!buf)
+ return -ENOMEM;
+ for (int i = 0; i <= strlen(str); i++)
+ buf[i] = (str[i] == '\n') ? '\0' : str[i];
+ ptr = buf;
+
+ while ((token = strsep(&ptr, " ")) && !ret) {
+ if (strlen(token)) {
+ int mode = 0;
+
+ while ((mode < TYPEC_MODE_MAX) &&
+ strcmp(token, mode_names[mode]))
+ mode++;
+ if (mode == TYPEC_MODE_MAX) {
+ ret = -EINVAL;
+ continue;
+ }
+
+ for (int i = 0; i < TYPEC_MODE_MAX; i++) {
+ if (list[i] == TYPEC_MODE_MAX) {
+ list[i] = mode;
+ break;
+ }
+ if (list[i] == mode) {
+ if (user_settings)
+ ret = -EINVAL;
+ break;
+ }
+ }
+ }
+ }
+ vfree(buf);
+
+ return ret;
+}
+
+int typec_mode_priorities_set(struct typec_port *port,
+ const char *user_priorities)
+{
+ int list[TYPEC_MODE_MAX];
+ int ret;
+
+ for (int i = 0; i < TYPEC_MODE_MAX; i++)
+ list[i] = TYPEC_MODE_MAX;
+
+ ret = typec_mode_parse_priority_string(user_priorities, list);
+ if (!ret)
+ ret = typec_mode_parse_priority_string(default_priorities, list);
+
+ if (!ret)
+ for (int i = 0; i < TYPEC_MODE_MAX; i++)
+ port->mode_priority_list[i] = list[i];
+
+ return ret;
+}
+
+static int port_altmode_supported(struct device *dev, void *data)
+{
+ if (!strcmp(dev->type->name, ALTERNATE_MODE_DEVICE_TYPE_NAME)) {
+ struct typec_altmode *alt = to_typec_altmode(dev);
+
+ if (*(int *)data == typec_svid_to_altmode(alt->svid))
+ return 1;
+ }
+ return 0;
+}
+
+static bool port_mode_supported(struct typec_port *port, int mode)
+{
+ if (mode >= TYPEC_MODE_MAX)
+ return false;
+ if (mode == TYPEC_USB4_MODE)
+ return !!(port->cap->usb_capability & USB_CAPABILITY_USB4);
+ return device_for_each_child(&port->dev, &mode, port_altmode_supported);
+}
+
+int typec_mode_priorities_get(struct typec_port *port, char *buf)
+{
+ ssize_t count = 0;
+
+ for (int i = 0; i < TYPEC_MODE_MAX; i++) {
+ int mode = port->mode_priority_list[i];
+
+ if (port_mode_supported(port, mode))
+ count += sysfs_emit_at(buf, count, "%s ", mode_names[mode]);
+ }
+
+ 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..c595c84e26a4
--- /dev/null
+++ b/drivers/usb/typec/mode_selection.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <linux/usb/typec_dp.h>
+#include <linux/usb/typec_tbt.h>
+
+static inline int typec_svid_to_altmode(const u16 svid)
+{
+ switch (svid) {
+ case USB_TYPEC_DP_SID:
+ return TYPEC_DP_ALTMODE;
+ case USB_TYPEC_TBT_SID:
+ return TYPEC_TBT_ALTMODE;
+ }
+ return TYPEC_MODE_MAX;
+}
+
+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..4f05c5f5c91d 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_DP_ALTMODE = 0,
+ TYPEC_TBT_ALTMODE,
+ TYPEC_USB4_MODE,
+ TYPEC_MODE_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);
--
2.50.0.727.gbf7dc18ff4-goog
Powered by blists - more mailing lists