Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

usb: typec: Add attribute file showing the USB Modes of the partner

This attribute file shows the supported USB modes (USB 2.0,
USB 3.0 and USB4) of the partner, and the currently active
mode.

The active mode is determined primarily by checking the
speed of the enumerated USB device. When USB Power Delivery
is supported, the active USB mode should be always the mode
that was used with the Enter_USB Message, regardless of the
result of the USB enumeration. The port drivers can
separately assign the mode with a dedicated API.

If USB Power Delivery Identity is supplied for the partner
device, the supported modes are extracted from it.

Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Link: https://lore.kernel.org/r/20241016131834.898599-3-heikki.krogerus@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Heikki Krogerus and committed by
Greg Kroah-Hartman
2140a952 8060bcb1

+141 -4
+14
Documentation/ABI/testing/sysfs-class-typec
··· 233 233 directory exists, it will have an attribute file for every VDO 234 234 in Discover Identity command result. 235 235 236 + What: /sys/class/typec/<port>-partner/usb_mode 237 + Date: November 2024 238 + Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> 239 + Description: The USB Modes that the partner device supports. The active mode 240 + is displayed in brackets. The active USB mode can be changed by 241 + writing to this file when the port driver is able to send Data 242 + Reset Message to the partner. That requires USB Power Delivery 243 + contract between the partner and the port. 244 + 245 + Valid values: 246 + - usb2 (USB 2.0) 247 + - usb3 (USB 3.2) 248 + - usb4 (USB4) 249 + 236 250 USB Type-C cable devices (eg. /sys/class/typec/port0-cable/) 237 251 238 252 Note: Electronically Marked Cables will have a device also for one cable plug
+120 -4
drivers/usb/typec/class.c
··· 618 618 /* ------------------------------------------------------------------------- */ 619 619 /* Type-C Partners */ 620 620 621 + /** 622 + * typec_partner_set_usb_mode - Assign active USB Mode for the partner 623 + * @partner: USB Type-C partner 624 + * @mode: USB Mode (USB2, USB3 or USB4) 625 + * 626 + * The port drivers can use this function to assign the active USB Mode to 627 + * @partner. The USB Mode can change for example due to Data Reset. 628 + */ 629 + void typec_partner_set_usb_mode(struct typec_partner *partner, enum usb_mode mode) 630 + { 631 + if (!partner || partner->usb_mode == mode) 632 + return; 633 + 634 + partner->usb_capability |= BIT(mode - 1); 635 + partner->usb_mode = mode; 636 + sysfs_notify(&partner->dev.kobj, NULL, "usb_mode"); 637 + } 638 + EXPORT_SYMBOL_GPL(typec_partner_set_usb_mode); 639 + 640 + static ssize_t 641 + usb_mode_show(struct device *dev, struct device_attribute *attr, char *buf) 642 + { 643 + struct typec_partner *partner = to_typec_partner(dev); 644 + int len = 0; 645 + int i; 646 + 647 + for (i = USB_MODE_USB2; i < USB_MODE_USB4 + 1; i++) { 648 + if (!(BIT(i - 1) & partner->usb_capability)) 649 + continue; 650 + 651 + if (i == partner->usb_mode) 652 + len += sysfs_emit_at(buf, len, "[%s] ", usb_modes[i]); 653 + else 654 + len += sysfs_emit_at(buf, len, "%s ", usb_modes[i]); 655 + } 656 + 657 + sysfs_emit_at(buf, len - 1, "\n"); 658 + 659 + return len; 660 + } 661 + 662 + static ssize_t usb_mode_store(struct device *dev, struct device_attribute *attr, 663 + const char *buf, size_t size) 664 + { 665 + struct typec_partner *partner = to_typec_partner(dev); 666 + struct typec_port *port = to_typec_port(dev->parent); 667 + int mode; 668 + int ret; 669 + 670 + if (!port->ops || !port->ops->enter_usb_mode) 671 + return -EOPNOTSUPP; 672 + 673 + mode = sysfs_match_string(usb_modes, buf); 674 + if (mode < 0) 675 + return mode; 676 + 677 + if (mode == partner->usb_mode) 678 + return size; 679 + 680 + ret = port->ops->enter_usb_mode(port, mode); 681 + if (ret) 682 + return ret; 683 + 684 + typec_partner_set_usb_mode(partner, mode); 685 + 686 + return size; 687 + } 688 + static DEVICE_ATTR_RW(usb_mode); 689 + 621 690 static ssize_t accessory_mode_show(struct device *dev, 622 691 struct device_attribute *attr, 623 692 char *buf) ··· 733 664 &dev_attr_supports_usb_power_delivery.attr, 734 665 &dev_attr_number_of_alternate_modes.attr, 735 666 &dev_attr_type.attr, 667 + &dev_attr_usb_mode.attr, 736 668 &dev_attr_usb_power_delivery_revision.attr, 737 669 NULL 738 670 }; ··· 741 671 static umode_t typec_partner_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) 742 672 { 743 673 struct typec_partner *partner = to_typec_partner(kobj_to_dev(kobj)); 674 + struct typec_port *port = to_typec_port(partner->dev.parent); 675 + 676 + if (attr == &dev_attr_usb_mode.attr) { 677 + if (!partner->usb_capability) 678 + return 0; 679 + if (!port->ops || !port->ops->enter_usb_mode) 680 + return 0444; 681 + } 744 682 745 683 if (attr == &dev_attr_number_of_alternate_modes.attr) { 746 684 if (partner->num_altmodes < 0) ··· 822 744 */ 823 745 int typec_partner_set_identity(struct typec_partner *partner) 824 746 { 825 - if (!partner->identity) 747 + u8 usb_capability = partner->usb_capability; 748 + struct device *dev = &partner->dev; 749 + struct usb_pd_identity *id; 750 + 751 + id = get_pd_identity(dev); 752 + if (!id) 826 753 return -EINVAL; 827 754 828 - typec_report_identity(&partner->dev); 755 + if (to_typec_port(dev->parent)->data_role == TYPEC_HOST) { 756 + u32 devcap = PD_VDO_UFP_DEVCAP(id->vdo[0]); 757 + 758 + if (devcap & (DEV_USB2_CAPABLE | DEV_USB2_BILLBOARD)) 759 + usb_capability |= USB_CAPABILITY_USB2; 760 + if (devcap & DEV_USB3_CAPABLE) 761 + usb_capability |= USB_CAPABILITY_USB3; 762 + if (devcap & DEV_USB4_CAPABLE) 763 + usb_capability |= USB_CAPABILITY_USB4; 764 + } else { 765 + usb_capability = PD_VDO_DFP_HOSTCAP(id->vdo[0]); 766 + } 767 + 768 + if (partner->usb_capability != usb_capability) { 769 + partner->usb_capability = usb_capability; 770 + sysfs_notify(&dev->kobj, NULL, "usb_mode"); 771 + } 772 + 773 + typec_report_identity(dev); 829 774 return 0; 830 775 } 831 776 EXPORT_SYMBOL_GPL(typec_partner_set_identity); ··· 1018 917 partner->usb_pd = desc->usb_pd; 1019 918 partner->accessory = desc->accessory; 1020 919 partner->num_altmodes = -1; 920 + partner->usb_capability = desc->usb_capability; 1021 921 partner->pd_revision = desc->pd_revision; 1022 922 partner->svdm_version = port->cap->svdm_version; 1023 923 partner->attach = desc->attach; ··· 1037 935 partner->dev.parent = &port->dev; 1038 936 partner->dev.type = &typec_partner_dev_type; 1039 937 dev_set_name(&partner->dev, "%s-partner", dev_name(&port->dev)); 938 + 939 + if (port->usb2_dev) { 940 + partner->usb_capability |= USB_CAPABILITY_USB2; 941 + partner->usb_mode = USB_MODE_USB2; 942 + } 943 + if (port->usb3_dev) { 944 + partner->usb_capability |= USB_CAPABILITY_USB2 | USB_CAPABILITY_USB3; 945 + partner->usb_mode = USB_MODE_USB3; 946 + } 1040 947 1041 948 ret = device_register(&partner->dev); 1042 949 if (ret) { ··· 2046 1935 struct typec_port *port = container_of(con, struct typec_port, con); 2047 1936 struct typec_partner *partner = typec_get_partner(port); 2048 1937 struct usb_device *udev = to_usb_device(dev); 1938 + enum usb_mode usb_mode; 2049 1939 2050 - if (udev->speed < USB_SPEED_SUPER) 1940 + if (udev->speed < USB_SPEED_SUPER) { 1941 + usb_mode = USB_MODE_USB2; 2051 1942 port->usb2_dev = dev; 2052 - else 1943 + } else { 1944 + usb_mode = USB_MODE_USB3; 2053 1945 port->usb3_dev = dev; 1946 + } 2054 1947 2055 1948 if (partner) { 1949 + typec_partner_set_usb_mode(partner, usb_mode); 2056 1950 typec_partner_link_device(partner, dev); 2057 1951 put_device(&partner->dev); 2058 1952 }
+2
drivers/usb/typec/class.h
··· 35 35 int num_altmodes; 36 36 u16 pd_revision; /* 0300H = "3.0" */ 37 37 enum usb_pd_svdm_ver svdm_version; 38 + enum usb_mode usb_mode; 39 + u8 usb_capability; 38 40 39 41 struct usb_power_delivery *pd; 40 42
+5
include/linux/usb/typec.h
··· 220 220 * @accessory: Audio, Debug or none. 221 221 * @identity: Discover Identity command data 222 222 * @pd_revision: USB Power Delivery Specification Revision if supported 223 + * @usb_capability: Supported USB Modes 223 224 * @attach: Notification about attached USB device 224 225 * @deattach: Notification about removed USB device 225 226 * ··· 238 237 enum typec_accessory accessory; 239 238 struct usb_pd_identity *identity; 240 239 u16 pd_revision; /* 0300H = "3.0" */ 240 + u8 usb_capability; 241 241 242 242 void (*attach)(struct typec_partner *partner, struct device *dev); 243 243 void (*deattach)(struct typec_partner *partner, struct device *dev); ··· 254 252 * @pd_get: Get available USB Power Delivery Capabilities. 255 253 * @pd_set: Set USB Power Delivery Capabilities. 256 254 * @default_usb_mode_set: USB Mode to be used by default with Enter_USB Message 255 + * @enter_usb_mode: Change the active USB Mode 257 256 */ 258 257 struct typec_operations { 259 258 int (*try_role)(struct typec_port *port, int role); ··· 266 263 struct usb_power_delivery **(*pd_get)(struct typec_port *port); 267 264 int (*pd_set)(struct typec_port *port, struct usb_power_delivery *pd); 268 265 int (*default_usb_mode_set)(struct typec_port *port, enum usb_mode mode); 266 + int (*enter_usb_mode)(struct typec_port *port, enum usb_mode mode); 269 267 }; 270 268 271 269 enum usb_pd_svdm_ver { ··· 369 365 int typec_partner_set_usb_power_delivery(struct typec_partner *partner, 370 366 struct usb_power_delivery *pd); 371 367 368 + void typec_partner_set_usb_mode(struct typec_partner *partner, enum usb_mode usb_mode); 372 369 void typec_port_set_usb_mode(struct typec_port *port, enum usb_mode mode); 373 370 374 371 /**