1 // SPDX-License-Identifier: GPL-2.0
3 * USB Type-C Connector Class Port Mapping Utility
5 * Copyright (C) 2021, Intel Corporation
6 * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
9 #include <linux/acpi.h>
10 #include <linux/usb.h>
11 #include <linux/usb/typec.h>
16 struct list_head list;
21 static int acpi_pld_match(const struct acpi_pld_info *pld1,
22 const struct acpi_pld_info *pld2)
28 * To speed things up, first checking only the group_position. It seems
29 * to often have the first unique value in the _PLD.
31 if (pld1->group_position == pld2->group_position)
32 return !memcmp(pld1, pld2, sizeof(struct acpi_pld_info));
37 static void *get_pld(struct device *dev)
40 struct acpi_pld_info *pld;
43 if (!has_acpi_companion(dev))
46 status = acpi_get_physical_device_location(ACPI_HANDLE(dev), &pld);
47 if (ACPI_FAILURE(status))
56 static void free_pld(void *pld)
63 static int __link_port(struct typec_port *con, struct port_node *node)
67 ret = sysfs_create_link(&node->dev->kobj, &con->dev.kobj, "connector");
71 ret = sysfs_create_link(&con->dev.kobj, &node->dev->kobj,
74 sysfs_remove_link(&node->dev->kobj, "connector");
78 list_add_tail(&node->list, &con->port_list);
83 static int link_port(struct typec_port *con, struct port_node *node)
87 mutex_lock(&con->port_list_lock);
88 ret = __link_port(con, node);
89 mutex_unlock(&con->port_list_lock);
94 static void __unlink_port(struct typec_port *con, struct port_node *node)
96 sysfs_remove_link(&con->dev.kobj, dev_name(node->dev));
97 sysfs_remove_link(&node->dev->kobj, "connector");
98 list_del(&node->list);
101 static void unlink_port(struct typec_port *con, struct port_node *node)
103 mutex_lock(&con->port_list_lock);
104 __unlink_port(con, node);
105 mutex_unlock(&con->port_list_lock);
108 static struct port_node *create_port_node(struct device *port)
110 struct port_node *node;
112 node = kzalloc(sizeof(*node), GFP_KERNEL);
114 return ERR_PTR(-ENOMEM);
116 node->dev = get_device(port);
117 node->pld = get_pld(port);
122 static void remove_port_node(struct port_node *node)
124 put_device(node->dev);
129 static int connector_match(struct device *dev, const void *data)
131 const struct port_node *node = data;
133 if (!is_typec_port(dev))
136 return acpi_pld_match(to_typec_port(dev)->pld, node->pld);
139 static struct device *find_connector(struct port_node *node)
144 return class_find_device(&typec_class, NULL, node, connector_match);
148 * typec_link_port - Link a port to its connector
149 * @port: The port device
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.
154 * NOTE. The function increments the reference count of @port on success.
156 int typec_link_port(struct device *port)
158 struct device *connector;
159 struct port_node *node;
162 node = create_port_node(port);
164 return PTR_ERR(node);
166 connector = find_connector(node);
172 ret = link_port(to_typec_port(connector), node);
179 put_device(connector);
181 remove_port_node(node);
185 EXPORT_SYMBOL_GPL(typec_link_port);
187 static int port_match_and_unlink(struct device *connector, void *port)
189 struct port_node *node;
190 struct port_node *tmp;
193 if (!is_typec_port(connector))
196 mutex_lock(&to_typec_port(connector)->port_list_lock);
197 list_for_each_entry_safe(node, tmp, &to_typec_port(connector)->port_list, list) {
198 ret = node->dev == port;
200 unlink_port(to_typec_port(connector), node);
201 remove_port_node(node);
202 put_device(connector);
206 mutex_unlock(&to_typec_port(connector)->port_list_lock);
212 * typec_unlink_port - Unlink port from its connector
213 * @port: The port device
215 * Removes the symlink "connector" and decrements the reference count of @port.
217 void typec_unlink_port(struct device *port)
219 class_for_each_device(&typec_class, NULL, port, port_match_and_unlink);
221 EXPORT_SYMBOL_GPL(typec_unlink_port);
223 static int each_port(struct device *port, void *connector)
225 struct port_node *node;
228 node = create_port_node(port);
230 return PTR_ERR(node);
232 if (!connector_match(connector, node)) {
233 remove_port_node(node);
237 ret = link_port(to_typec_port(connector), node);
239 remove_port_node(node->pld);
243 get_device(connector);
248 int typec_link_ports(struct typec_port *con)
252 con->pld = get_pld(&con->dev);
256 ret = usb_for_each_port(&con->dev, each_port);
258 typec_unlink_ports(con);
263 void typec_unlink_ports(struct typec_port *con)
265 struct port_node *node;
266 struct port_node *tmp;
268 mutex_lock(&con->port_list_lock);
270 list_for_each_entry_safe(node, tmp, &con->port_list, list) {
271 __unlink_port(con, node);
272 remove_port_node(node);
273 put_device(&con->dev);
276 mutex_unlock(&con->port_list_lock);