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 <akuchynski@chromium.org>
Reviewed-by: Benson Leung <bleung@chromium.org>
Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Link: https://patch.msgid.link/20260119131824.2529334-4-akuchynski@chromium.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Andrei Kuchynski 2026-01-19 13:18:20 +00:00 committed by Greg Kroah-Hartman
parent 4ec128733f
commit 027b304ca3
3 changed files with 101 additions and 1 deletions

View file

@ -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/<port>/<alt-mode>/priority
Date: July 2025
Contact: Andrei Kuchynski <akuchynski@chromium.org>
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/<port>-partner/accessory_mode

View file

@ -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;

View file

@ -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;