diff options
-rw-r--r-- | Documentation/ABI/testing/sysfs-bus-usb | 9 | ||||
-rw-r--r-- | drivers/usb/typec/class.c | 108 | ||||
-rw-r--r-- | drivers/usb/typec/class.h | 16 | ||||
-rw-r--r-- | drivers/usb/typec/port-mapper.c | 9 | ||||
-rw-r--r-- | include/linux/usb/typec.h | 37 |
5 files changed, 169 insertions, 10 deletions
diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index a44bfe020061..2b7108e21977 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -313,6 +313,15 @@ Description: Inter-Chip SSIC devices support asymmetric lanes up to 4 lanes per direction. Devices before USB 3.2 are single lane (tx_lanes = 1) +What: /sys/bus/usb/devices/.../typec +Date: November 2023 +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> +Description: + Symlink to the USB Type-C partner device. USB Type-C partner + represents the component that communicates over the + Configuration Channel (CC signal on USB Type-C connectors and + cables) with the local port. + What: /sys/bus/usb/devices/usbX/bAlternateSetting Description: The current interface alternate setting number, in decimal. diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 9c1dbf3c00e0..2e0451bd336e 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -13,6 +13,7 @@ #include <linux/usb/pd_vdo.h> #include <linux/usb/typec_mux.h> #include <linux/usb/typec_retimer.h> +#include <linux/usb.h> #include "bus.h" #include "class.h" @@ -681,6 +682,33 @@ const struct device_type typec_partner_dev_type = { .release = typec_partner_release, }; +static void typec_partner_link_device(struct typec_partner *partner, struct device *dev) +{ + int ret; + + ret = sysfs_create_link(&dev->kobj, &partner->dev.kobj, "typec"); + if (ret) + return; + + ret = sysfs_create_link(&partner->dev.kobj, &dev->kobj, dev_name(dev)); + if (ret) { + sysfs_remove_link(&dev->kobj, "typec"); + return; + } + + if (partner->attach) + partner->attach(partner, dev); +} + +static void typec_partner_unlink_device(struct typec_partner *partner, struct device *dev) +{ + sysfs_remove_link(&partner->dev.kobj, dev_name(dev)); + sysfs_remove_link(&dev->kobj, "typec"); + + if (partner->deattach) + partner->deattach(partner, dev); +} + /** * typec_partner_set_identity - Report result from Discover Identity command * @partner: The partner updated identity values @@ -865,6 +893,8 @@ struct typec_partner *typec_register_partner(struct typec_port *port, partner->num_altmodes = -1; partner->pd_revision = desc->pd_revision; partner->svdm_version = port->cap->svdm_version; + partner->attach = desc->attach; + partner->deattach = desc->deattach; if (desc->identity) { /* @@ -887,6 +917,11 @@ struct typec_partner *typec_register_partner(struct typec_port *port, return ERR_PTR(ret); } + if (port->usb2_dev) + typec_partner_link_device(partner, port->usb2_dev); + if (port->usb3_dev) + typec_partner_link_device(partner, port->usb3_dev); + return partner; } EXPORT_SYMBOL_GPL(typec_register_partner); @@ -899,8 +934,19 @@ EXPORT_SYMBOL_GPL(typec_register_partner); */ void typec_unregister_partner(struct typec_partner *partner) { - if (!IS_ERR_OR_NULL(partner)) - device_unregister(&partner->dev); + struct typec_port *port; + + if (IS_ERR_OR_NULL(partner)) + return; + + port = to_typec_port(partner->dev.parent); + + if (port->usb2_dev) + typec_partner_unlink_device(partner, port->usb2_dev); + if (port->usb3_dev) + typec_partner_unlink_device(partner, port->usb3_dev); + + device_unregister(&partner->dev); } EXPORT_SYMBOL_GPL(typec_unregister_partner); @@ -1775,6 +1821,50 @@ static int partner_match(struct device *dev, void *data) return is_typec_partner(dev); } +static struct typec_partner *typec_get_partner(struct typec_port *port) +{ + struct device *dev; + + dev = device_find_child(&port->dev, NULL, partner_match); + if (!dev) + return NULL; + + return to_typec_partner(dev); +} + +static void typec_partner_attach(struct typec_connector *con, struct device *dev) +{ + struct typec_port *port = container_of(con, struct typec_port, con); + struct typec_partner *partner = typec_get_partner(port); + struct usb_device *udev = to_usb_device(dev); + + if (udev->speed < USB_SPEED_SUPER) + port->usb2_dev = dev; + else + port->usb3_dev = dev; + + if (partner) { + typec_partner_link_device(partner, dev); + put_device(&partner->dev); + } +} + +static void typec_partner_deattach(struct typec_connector *con, struct device *dev) +{ + struct typec_port *port = container_of(con, struct typec_port, con); + struct typec_partner *partner = typec_get_partner(port); + + if (partner) { + typec_partner_unlink_device(partner, dev); + put_device(&partner->dev); + } + + if (port->usb2_dev == dev) + port->usb2_dev = NULL; + else if (port->usb3_dev == dev) + port->usb3_dev = NULL; +} + /** * typec_set_data_role - Report data role change * @port: The USB Type-C Port where the role was changed @@ -1784,7 +1874,7 @@ static int partner_match(struct device *dev, void *data) */ void typec_set_data_role(struct typec_port *port, enum typec_data_role role) { - struct device *partner_dev; + struct typec_partner *partner; if (port->data_role == role) return; @@ -1793,14 +1883,14 @@ void typec_set_data_role(struct typec_port *port, enum typec_data_role role) sysfs_notify(&port->dev.kobj, NULL, "data_role"); kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); - partner_dev = device_find_child(&port->dev, NULL, partner_match); - if (!partner_dev) + partner = typec_get_partner(port); + if (!partner) return; - if (to_typec_partner(partner_dev)->identity) - typec_product_type_notify(partner_dev); + if (partner->identity) + typec_product_type_notify(&partner->dev); - put_device(partner_dev); + put_device(&partner->dev); } EXPORT_SYMBOL_GPL(typec_set_data_role); @@ -2251,6 +2341,8 @@ struct typec_port *typec_register_port(struct device *parent, port->ops = cap->ops; port->port_type = cap->type; port->prefer_role = cap->prefer_role; + port->con.attach = typec_partner_attach; + port->con.deattach = typec_partner_deattach; device_initialize(&port->dev); port->dev.class = &typec_class; diff --git a/drivers/usb/typec/class.h b/drivers/usb/typec/class.h index 673b2952b074..c36761ba3f59 100644 --- a/drivers/usb/typec/class.h +++ b/drivers/usb/typec/class.h @@ -8,6 +8,7 @@ struct typec_mux; struct typec_switch; +struct usb_device; struct typec_plug { struct device dev; @@ -35,6 +36,9 @@ struct typec_partner { enum usb_pd_svdm_ver svdm_version; struct usb_power_delivery *pd; + + void (*attach)(struct typec_partner *partner, struct device *dev); + void (*deattach)(struct typec_partner *partner, struct device *dev); }; struct typec_port { @@ -59,6 +63,18 @@ struct typec_port { const struct typec_capability *cap; const struct typec_operations *ops; + + struct typec_connector con; + + /* + * REVISIT: Only USB devices for now. If there are others, these need to + * be converted into a list. + * + * NOTE: These may be registered first before the typec_partner, so they + * will always have to be kept here instead of struct typec_partner. + */ + struct device *usb2_dev; + struct device *usb3_dev; }; #define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev) diff --git a/drivers/usb/typec/port-mapper.c b/drivers/usb/typec/port-mapper.c index a929e000d0e2..d42da5720a25 100644 --- a/drivers/usb/typec/port-mapper.c +++ b/drivers/usb/typec/port-mapper.c @@ -8,17 +8,22 @@ #include <linux/acpi.h> #include <linux/component.h> +#include <linux/usb.h> #include "class.h" static int typec_aggregate_bind(struct device *dev) { - return component_bind_all(dev, NULL); + struct typec_port *port = to_typec_port(dev); + + return component_bind_all(dev, &port->con); } static void typec_aggregate_unbind(struct device *dev) { - component_unbind_all(dev, NULL); + struct typec_port *port = to_typec_port(dev); + + component_unbind_all(dev, &port->con); } static const struct component_master_ops typec_aggregate_ops = { diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h index 8fa781207970..a05d6f6f2536 100644 --- a/include/linux/usb/typec.h +++ b/include/linux/usb/typec.h @@ -202,6 +202,8 @@ struct typec_cable_desc { * @accessory: Audio, Debug or none. * @identity: Discover Identity command data * @pd_revision: USB Power Delivery Specification Revision if supported + * @attach: Notification about attached USB device + * @deattach: Notification about removed USB device * * Details about a partner that is attached to USB Type-C port. If @identity * member exists when partner is registered, a directory named "identity" is @@ -217,6 +219,9 @@ struct typec_partner_desc { enum typec_accessory accessory; struct usb_pd_identity *identity; u16 pd_revision; /* 0300H = "3.0" */ + + void (*attach)(struct typec_partner *partner, struct device *dev); + void (*deattach)(struct typec_partner *partner, struct device *dev); }; /** @@ -335,4 +340,36 @@ int typec_port_set_usb_power_delivery(struct typec_port *port, struct usb_power_ int typec_partner_set_usb_power_delivery(struct typec_partner *partner, struct usb_power_delivery *pd); +/** + * struct typec_connector - Representation of Type-C port for external drivers + * @attach: notification about device removal + * @deattach: notification about device removal + * + * Drivers that control the USB and other ports (DisplayPorts, etc.), that are + * connected to the Type-C connectors, can use these callbacks to inform the + * Type-C connector class about connections and disconnections. That information + * can then be used by the typec-port drivers to power on or off parts that are + * needed or not needed - as an example, in USB mode if USB2 device is + * enumerated, USB3 components (retimers, phys, and what have you) do not need + * to be powered on. + * + * The attached (enumerated) devices will be liked with the typec-partner device. + */ +struct typec_connector { + void (*attach)(struct typec_connector *con, struct device *dev); + void (*deattach)(struct typec_connector *con, struct device *dev); +}; + +static inline void typec_attach(struct typec_connector *con, struct device *dev) +{ + if (con && con->attach) + con->attach(con, dev); +} + +static inline void typec_deattach(struct typec_connector *con, struct device *dev) +{ + if (con && con->deattach) + con->deattach(con, dev); +} + #endif /* __LINUX_USB_TYPEC_H */ |