On Mon, Nov 24, 2025 at 12:46:39PM +0000, Andrei Kuchynski wrote: > 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 > --- > 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 38e101c17a00..737b76828b50 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 a5327e444265..049d1829be98 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_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_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; > } > @@ -2484,6 +2565,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)) > @@ -2502,6 +2584,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 f7db3bd4c90e..2c3b6bec2eca 100644 > --- a/include/linux/usb/typec_altmode.h > +++ b/include/linux/usb/typec_altmode.h > @@ -28,6 +28,7 @@ struct typec_altmode { > int mode; > u32 vdo; > unsigned int active:1; > + u8 priority; > > char *desc; > const struct typec_altmode_ops *ops; > -- > 2.52.0.rc2.455.g230fcf2819-goog >