This patch introduces new sysfs attribute to enable user control over
Type-C mode selection. Writing a boolean '1' to this attribute starts the
mode selection process, while writing '0' stops it.
Signed-off-by: Andrei Kuchynski <akuchynski@chromium.org>
---
Documentation/ABI/testing/sysfs-class-typec | 11 ++++++
drivers/usb/typec/class.c | 37 +++++++++++++++++++++
2 files changed, 48 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-class-typec b/Documentation/ABI/testing/sysfs-class-typec
index dab3e4e727b6..7addf0e69c5c 100644
--- a/Documentation/ABI/testing/sysfs-class-typec
+++ b/Documentation/ABI/testing/sysfs-class-typec
@@ -258,6 +258,17 @@ Description: The USB Modes that the partner device supports. The active mode
- usb3 (USB 3.2)
- usb4 (USB4)
+What: /sys/class/typec/<port>-partner/mode_selection
+Date: August 2025
+Contact: Andrei Kuchynski <akuchynski@chromium.org>
+Description: Mode selection is activated by writing boolean 1 to the
+ file. Conversely, writing boolean 0 will cancel any ongoing selection
+ process and exit the currently active mode, if any. The attribute
+ returns "on" when mode selection is either in progress or complete,
+ and "off" otherwise.
+ This attribute is only present if the kernel supports AP driven mode
+ entry, where the Application Processor manages USB Type-C alt-modes.
+
USB Type-C cable devices (eg. /sys/class/typec/port0-cable/)
Note: Electronically Marked Cables will have a device also for one cable plug
diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
index aaab2e1e98b4..b66fe62f282d 100644
--- a/drivers/usb/typec/class.c
+++ b/drivers/usb/typec/class.c
@@ -763,6 +763,35 @@ static ssize_t number_of_alternate_modes_show(struct device *dev, struct device_
}
static DEVICE_ATTR_RO(number_of_alternate_modes);
+static ssize_t mode_selection_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ const bool ret = typec_mode_selection_is_pending(to_typec_partner(dev));
+
+ return sprintf(buf, "%s\n", str_on_off(ret));
+}
+
+static ssize_t mode_selection_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct typec_partner *partner = to_typec_partner(dev);
+ bool start;
+ int ret = kstrtobool(buf, &start);
+
+ if (!ret) {
+ if (start)
+ ret = typec_mode_selection_start(partner);
+ else
+ ret = typec_mode_selection_reset(partner);
+ }
+
+ if (ret)
+ return ret;
+
+ return size;
+}
+static DEVICE_ATTR_RW(mode_selection);
+
static struct attribute *typec_partner_attrs[] = {
&dev_attr_accessory_mode.attr,
&dev_attr_supports_usb_power_delivery.attr,
@@ -770,6 +799,7 @@ static struct attribute *typec_partner_attrs[] = {
&dev_attr_type.attr,
&dev_attr_usb_mode.attr,
&dev_attr_usb_power_delivery_revision.attr,
+ &dev_attr_mode_selection.attr,
NULL
};
@@ -794,6 +824,10 @@ static umode_t typec_partner_attr_is_visible(struct kobject *kobj, struct attrib
if (!get_pd_product_type(kobj_to_dev(kobj)))
return 0;
+ if (attr == &dev_attr_mode_selection.attr)
+ if (!port->mode_control)
+ return 0;
+
return attr->mode;
}
@@ -1097,6 +1131,8 @@ struct typec_partner *typec_register_partner(struct typec_port *port,
typec_partner_link_device(partner, port->usb3_dev);
mutex_unlock(&port->partner_link_lock);
+ typec_mode_selection_add_partner(partner);
+
return partner;
}
EXPORT_SYMBOL_GPL(typec_register_partner);
@@ -1114,6 +1150,7 @@ void typec_unregister_partner(struct typec_partner *partner)
if (IS_ERR_OR_NULL(partner))
return;
+ typec_mode_selection_remove_partner(partner);
port = to_typec_port(partner->dev.parent);
mutex_lock(&port->partner_link_lock);
--
2.51.0.384.g4c02a37b29-goog