mirror of
https://github.com/torvalds/linux.git
synced 2026-03-08 03:44:45 +01:00
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:
parent
4ec128733f
commit
027b304ca3
3 changed files with 101 additions and 1 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue