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

usb: typec: Port mapping utility

Adding functions that can be used to link/unlink ports -
USB ports, TBT3/USB4 ports, DisplayPorts and so on - to
the USB Type-C connectors they are attached to inside a
system. The symlink that is created for the port device is
named "connector".

Initially only ACPI is supported. ACPI port object shares
the _PLD (Physical Location of Device) with the USB Type-C
connector that it's attached to.

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

authored by

Heikki Krogerus and committed by
Greg Kroah-Hartman
ae196ddb 4050f268

+248 -2
+1 -1
drivers/usb/typec/Makefile
··· 1 1 # SPDX-License-Identifier: GPL-2.0 2 2 obj-$(CONFIG_TYPEC) += typec.o 3 - typec-y := class.o mux.o bus.o 3 + typec-y := class.o mux.o bus.o port-mapper.o 4 4 obj-$(CONFIG_TYPEC) += altmodes/ 5 5 obj-$(CONFIG_TYPEC_TCPM) += tcpm/ 6 6 obj-$(CONFIG_TYPEC_UCSI) += ucsi/
+6 -1
drivers/usb/typec/class.c
··· 18 18 19 19 static DEFINE_IDA(typec_index_ida); 20 20 21 - static struct class typec_class = { 21 + struct class typec_class = { 22 22 .name = "typec", 23 23 .owner = THIS_MODULE, 24 24 }; ··· 1601 1601 ida_destroy(&port->mode_ids); 1602 1602 typec_switch_put(port->sw); 1603 1603 typec_mux_put(port->mux); 1604 + free_pld(port->pld); 1604 1605 kfree(port->cap); 1605 1606 kfree(port); 1606 1607 } ··· 1984 1983 1985 1984 ida_init(&port->mode_ids); 1986 1985 mutex_init(&port->port_type_lock); 1986 + mutex_init(&port->port_list_lock); 1987 + INIT_LIST_HEAD(&port->port_list); 1987 1988 1988 1989 port->id = id; 1989 1990 port->ops = cap->ops; ··· 2026 2023 put_device(&port->dev); 2027 2024 return ERR_PTR(ret); 2028 2025 } 2026 + 2027 + port->pld = get_pld(&port->dev); 2029 2028 2030 2029 return port; 2031 2030 }
+9
drivers/usb/typec/class.h
··· 54 54 55 55 const struct typec_capability *cap; 56 56 const struct typec_operations *ops; 57 + 58 + struct list_head port_list; 59 + struct mutex port_list_lock; /* Port list lock */ 60 + 61 + void *pld; 57 62 }; 58 63 59 64 #define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev) ··· 77 72 #define is_typec_port(dev) ((dev)->type == &typec_port_dev_type) 78 73 79 74 extern struct class typec_mux_class; 75 + extern struct class typec_class; 76 + 77 + void *get_pld(struct device *dev); 78 + void free_pld(void *pld); 80 79 81 80 #endif /* __USB_TYPEC_CLASS__ */
+219
drivers/usb/typec/port-mapper.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * USB Type-C Connector Class Port Mapping Utility 4 + * 5 + * Copyright (C) 2021, Intel Corporation 6 + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> 7 + */ 8 + 9 + #include <linux/acpi.h> 10 + #include <linux/usb.h> 11 + #include <linux/usb/typec.h> 12 + 13 + #include "class.h" 14 + 15 + struct port_node { 16 + struct list_head list; 17 + struct device *dev; 18 + void *pld; 19 + }; 20 + 21 + static int acpi_pld_match(const struct acpi_pld_info *pld1, 22 + const struct acpi_pld_info *pld2) 23 + { 24 + if (!pld1 || !pld2) 25 + return 0; 26 + 27 + /* 28 + * To speed things up, first checking only the group_position. It seems 29 + * to often have the first unique value in the _PLD. 30 + */ 31 + if (pld1->group_position == pld2->group_position) 32 + return !memcmp(pld1, pld2, sizeof(struct acpi_pld_info)); 33 + 34 + return 0; 35 + } 36 + 37 + void *get_pld(struct device *dev) 38 + { 39 + #ifdef CONFIG_ACPI 40 + struct acpi_pld_info *pld; 41 + acpi_status status; 42 + 43 + if (!has_acpi_companion(dev)) 44 + return NULL; 45 + 46 + status = acpi_get_physical_device_location(ACPI_HANDLE(dev), &pld); 47 + if (ACPI_FAILURE(status)) 48 + return NULL; 49 + 50 + return pld; 51 + #else 52 + return NULL; 53 + #endif 54 + } 55 + 56 + void free_pld(void *pld) 57 + { 58 + #ifdef CONFIG_ACPI 59 + ACPI_FREE(pld); 60 + #endif 61 + } 62 + 63 + static int __link_port(struct typec_port *con, struct port_node *node) 64 + { 65 + int ret; 66 + 67 + ret = sysfs_create_link(&node->dev->kobj, &con->dev.kobj, "connector"); 68 + if (ret) 69 + return ret; 70 + 71 + ret = sysfs_create_link(&con->dev.kobj, &node->dev->kobj, 72 + dev_name(node->dev)); 73 + if (ret) { 74 + sysfs_remove_link(&node->dev->kobj, "connector"); 75 + return ret; 76 + } 77 + 78 + list_add_tail(&node->list, &con->port_list); 79 + 80 + return 0; 81 + } 82 + 83 + static int link_port(struct typec_port *con, struct port_node *node) 84 + { 85 + int ret; 86 + 87 + mutex_lock(&con->port_list_lock); 88 + ret = __link_port(con, node); 89 + mutex_unlock(&con->port_list_lock); 90 + 91 + return ret; 92 + } 93 + 94 + static void __unlink_port(struct typec_port *con, struct port_node *node) 95 + { 96 + sysfs_remove_link(&con->dev.kobj, dev_name(node->dev)); 97 + sysfs_remove_link(&node->dev->kobj, "connector"); 98 + list_del(&node->list); 99 + } 100 + 101 + static void unlink_port(struct typec_port *con, struct port_node *node) 102 + { 103 + mutex_lock(&con->port_list_lock); 104 + __unlink_port(con, node); 105 + mutex_unlock(&con->port_list_lock); 106 + } 107 + 108 + static struct port_node *create_port_node(struct device *port) 109 + { 110 + struct port_node *node; 111 + 112 + node = kzalloc(sizeof(*node), GFP_KERNEL); 113 + if (!node) 114 + return ERR_PTR(-ENOMEM); 115 + 116 + node->dev = get_device(port); 117 + node->pld = get_pld(port); 118 + 119 + return node; 120 + } 121 + 122 + static void remove_port_node(struct port_node *node) 123 + { 124 + put_device(node->dev); 125 + free_pld(node->pld); 126 + kfree(node); 127 + } 128 + 129 + static int connector_match(struct device *dev, const void *data) 130 + { 131 + const struct port_node *node = data; 132 + 133 + if (!is_typec_port(dev)) 134 + return 0; 135 + 136 + return acpi_pld_match(to_typec_port(dev)->pld, node->pld); 137 + } 138 + 139 + static struct device *find_connector(struct port_node *node) 140 + { 141 + if (!node->pld) 142 + return NULL; 143 + 144 + return class_find_device(&typec_class, NULL, node, connector_match); 145 + } 146 + 147 + /** 148 + * typec_link_port - Link a port to its connector 149 + * @port: The port device 150 + * 151 + * Find the connector of @port and create symlink named "connector" for it. 152 + * Returns 0 on success, or errno in case of a failure. 153 + * 154 + * NOTE. The function increments the reference count of @port on success. 155 + */ 156 + int typec_link_port(struct device *port) 157 + { 158 + struct device *connector; 159 + struct port_node *node; 160 + int ret = 0; 161 + 162 + node = create_port_node(port); 163 + if (IS_ERR(node)) 164 + return PTR_ERR(node); 165 + 166 + connector = find_connector(node); 167 + if (!connector) 168 + goto remove_node; 169 + 170 + ret = link_port(to_typec_port(connector), node); 171 + if (ret) 172 + goto put_connector; 173 + 174 + return 0; 175 + 176 + put_connector: 177 + put_device(connector); 178 + remove_node: 179 + remove_port_node(node); 180 + 181 + return ret; 182 + } 183 + EXPORT_SYMBOL_GPL(typec_link_port); 184 + 185 + static int port_match_and_unlink(struct device *connector, void *port) 186 + { 187 + struct port_node *node; 188 + struct port_node *tmp; 189 + int ret = 0; 190 + 191 + if (!is_typec_port(connector)) 192 + return 0; 193 + 194 + mutex_lock(&to_typec_port(connector)->port_list_lock); 195 + list_for_each_entry_safe(node, tmp, &to_typec_port(connector)->port_list, list) { 196 + ret = node->dev == port; 197 + if (ret) { 198 + unlink_port(to_typec_port(connector), node); 199 + remove_port_node(node); 200 + put_device(connector); 201 + break; 202 + } 203 + } 204 + mutex_unlock(&to_typec_port(connector)->port_list_lock); 205 + 206 + return ret; 207 + } 208 + 209 + /** 210 + * typec_unlink_port - Unlink port from its connector 211 + * @port: The port device 212 + * 213 + * Removes the symlink "connector" and decrements the reference count of @port. 214 + */ 215 + void typec_unlink_port(struct device *port) 216 + { 217 + class_for_each_device(&typec_class, NULL, port, port_match_and_unlink); 218 + } 219 + EXPORT_SYMBOL_GPL(typec_unlink_port);
+13
include/linux/usb/typec.h
··· 298 298 void typec_partner_set_svdm_version(struct typec_partner *partner, 299 299 enum usb_pd_svdm_ver svdm_version); 300 300 int typec_get_negotiated_svdm_version(struct typec_port *port); 301 + 302 + #if IS_REACHABLE(CONFIG_TYPEC) 303 + int typec_link_port(struct device *port); 304 + void typec_unlink_port(struct device *port); 305 + #else 306 + static inline int typec_link_port(struct device *port) 307 + { 308 + return 0; 309 + } 310 + 311 + static inline void typec_unlink_port(struct device *port) { } 312 + #endif 313 + 301 314 #endif /* __LINUX_USB_TYPEC_H */