From 027b304ca3f6989f6946b5b3bcc727cf3f54774f Mon Sep 17 00:00:00 2001 From: Andrei Kuchynski Date: Mon, 19 Jan 2026 13:18:20 +0000 Subject: [PATCH] usb: typec: Expose alternate mode priority via sysfs This patch introduces a priority sysfs attribute to the USB Type-C alternate mode port interface. This new attribute allows user-space to configure the numeric priority of alternate modes managing their preferred order of operation. If a new priority value conflicts with an existing mode's priority, the priorities of the conflicting mode and all subsequent modes are automatically incremented to ensure uniqueness. Signed-off-by: Andrei Kuchynski Reviewed-by: Benson Leung Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260119131824.2529334-4-akuchynski@chromium.org Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-class-typec | 11 +++ drivers/usb/typec/class.c | 90 ++++++++++++++++++++- include/linux/usb/typec_altmode.h | 1 + 3 files changed, 101 insertions(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/sysfs-class-typec b/Documentation/ABI/testing/sysfs-class-typec index 38e101c17a004..737b76828b509 100644 --- a/Documentation/ABI/testing/sysfs-class-typec +++ b/Documentation/ABI/testing/sysfs-class-typec @@ -162,6 +162,17 @@ Description: Lists the supported USB Modes. The default USB mode that is used - usb3 (USB 3.2) - usb4 (USB4) +What: /sys/class/typec///priority +Date: July 2025 +Contact: Andrei Kuchynski +Description: + Displays and allows setting the priority for a specific alternate mode. + The priority is an integer in the range 0-255. A lower numerical value + indicates a higher priority (0 is the highest). + If the new value is already in use by another mode, the priority of the + conflicting mode and any subsequent modes will be incremented until they + are all unique. + USB Type-C partner devices (eg. /sys/class/typec/port0-partner/) What: /sys/class/typec/-partner/accessory_mode diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 0f12d6120511b..a48c447125184 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -445,11 +445,88 @@ svid_show(struct device *dev, struct device_attribute *attr, char *buf) } static DEVICE_ATTR_RO(svid); +static int increment_duplicated_priority(struct device *dev, void *data) +{ + if (is_typec_port_altmode(dev)) { + struct typec_altmode **alt_target = (struct typec_altmode **)data; + struct typec_altmode *alt = to_typec_altmode(dev); + + if (alt != *alt_target && alt->priority == (*alt_target)->priority) { + alt->priority++; + *alt_target = alt; + return 1; + } + } + return 0; +} + +static int find_duplicated_priority(struct device *dev, void *data) +{ + if (is_typec_port_altmode(dev)) { + struct typec_altmode **alt_target = (struct typec_altmode **)data; + struct typec_altmode *alt = to_typec_altmode(dev); + + if (alt != *alt_target && alt->priority == (*alt_target)->priority) + return 1; + } + return 0; +} + +static int typec_mode_set_priority(struct typec_altmode *alt, const u8 priority) +{ + struct typec_port *port = to_typec_port(alt->dev.parent); + const u8 old_priority = alt->priority; + int res = 1; + + alt->priority = priority; + while (res) { + res = device_for_each_child(&port->dev, &alt, find_duplicated_priority); + if (res) { + alt->priority++; + if (alt->priority == 0) { + alt->priority = old_priority; + return -EOVERFLOW; + } + } + } + + res = 1; + alt->priority = priority; + while (res) + res = device_for_each_child(&port->dev, &alt, + increment_duplicated_priority); + + return 0; +} + +static ssize_t priority_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + u8 val; + int err = kstrtou8(buf, 10, &val); + + if (!err) + err = typec_mode_set_priority(to_typec_altmode(dev), val); + + if (!err) + return size; + return err; +} + +static ssize_t priority_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", to_typec_altmode(dev)->priority); +} +static DEVICE_ATTR_RW(priority); + static struct attribute *typec_altmode_attrs[] = { &dev_attr_active.attr, &dev_attr_mode.attr, &dev_attr_svid.attr, &dev_attr_vdo.attr, + &dev_attr_priority.attr, NULL }; @@ -459,11 +536,15 @@ static umode_t typec_altmode_attr_is_visible(struct kobject *kobj, struct typec_altmode *adev = to_typec_altmode(kobj_to_dev(kobj)); struct typec_port *port = typec_altmode2port(adev); - if (attr == &dev_attr_active.attr) + if (attr == &dev_attr_active.attr) { if (!is_typec_port(adev->dev.parent)) { if (!port->mode_control || !adev->ops || !adev->ops->activate) return 0444; } + } else if (attr == &dev_attr_priority.attr) { + if (!is_typec_port(adev->dev.parent) || !port->mode_control) + return 0; + } return attr->mode; } @@ -2498,6 +2579,7 @@ typec_port_register_altmode(struct typec_port *port, struct typec_altmode *adev; struct typec_mux *mux; struct typec_retimer *retimer; + int ret; mux = typec_mux_get(&port->dev); if (IS_ERR(mux)) @@ -2516,6 +2598,12 @@ typec_port_register_altmode(struct typec_port *port, } else { to_altmode(adev)->mux = mux; to_altmode(adev)->retimer = retimer; + + ret = typec_mode_set_priority(adev, 0); + if (ret) { + typec_unregister_altmode(adev); + return ERR_PTR(ret); + } } return adev; diff --git a/include/linux/usb/typec_altmode.h b/include/linux/usb/typec_altmode.h index 9197a4637a938..7e6c02d74b54f 100644 --- a/include/linux/usb/typec_altmode.h +++ b/include/linux/usb/typec_altmode.h @@ -36,6 +36,7 @@ struct typec_altmode { int mode; u32 vdo; unsigned int active:1; + u8 priority; char *desc; const struct typec_altmode_ops *ops; -- 2.47.3