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/component.h>
11 #include <linux/thunderbolt.h>
12 #include <linux/usb.h>
13
14 #include "class.h"
15
typec_aggregate_bind(struct device * dev)16 static int typec_aggregate_bind(struct device *dev)
17 {
18 struct typec_port *port = to_typec_port(dev);
19
20 return component_bind_all(dev, &port->con);
21 }
22
typec_aggregate_unbind(struct device * dev)23 static void typec_aggregate_unbind(struct device *dev)
24 {
25 struct typec_port *port = to_typec_port(dev);
26
27 component_unbind_all(dev, &port->con);
28 }
29
30 static const struct component_master_ops typec_aggregate_ops = {
31 .bind = typec_aggregate_bind,
32 .unbind = typec_aggregate_unbind,
33 };
34
35 struct each_port_arg {
36 struct typec_port *port;
37 struct component_match *match;
38 };
39
usb4_port_compare(struct device * dev,void * fwnode)40 static int usb4_port_compare(struct device *dev, void *fwnode)
41 {
42 return usb4_usb3_port_match(dev, fwnode);
43 }
44
typec_port_compare(struct device * dev,void * fwnode)45 static int typec_port_compare(struct device *dev, void *fwnode)
46 {
47 return device_match_fwnode(dev, fwnode);
48 }
49
typec_port_match(struct device * dev,void * data)50 static int typec_port_match(struct device *dev, void *data)
51 {
52 struct acpi_device *adev = to_acpi_device(dev);
53 struct each_port_arg *arg = data;
54 struct acpi_device *con_adev;
55
56 con_adev = ACPI_COMPANION(&arg->port->dev);
57 if (con_adev == adev)
58 return 0;
59
60 if (con_adev->pld_crc == adev->pld_crc) {
61 struct fwnode_handle *adev_fwnode = acpi_fwnode_handle(adev);
62
63 component_match_add(&arg->port->dev, &arg->match, typec_port_compare,
64 adev_fwnode);
65
66 /*
67 * If dev is USB 3.x port, it may have reference to the
68 * USB4 host interface in which case we can also link the
69 * Type-C port with the USB4 port.
70 */
71 if (fwnode_property_present(adev_fwnode, "usb4-host-interface"))
72 component_match_add(&arg->port->dev, &arg->match,
73 usb4_port_compare, adev_fwnode);
74 }
75
76 return 0;
77 }
78
typec_link_ports(struct typec_port * con)79 int typec_link_ports(struct typec_port *con)
80 {
81 struct each_port_arg arg = { .port = con, .match = NULL };
82
83 if (!has_acpi_companion(&con->dev))
84 return 0;
85
86 acpi_bus_for_each_dev(typec_port_match, &arg);
87 if (!arg.match)
88 return 0;
89
90 /*
91 * REVISIT: Now each connector can have only a single component master.
92 * So far only the USB ports connected to the USB Type-C connector share
93 * the _PLD with it, but if there one day is something else (like maybe
94 * the DisplayPort ACPI device object) that also shares the _PLD with
95 * the connector, every one of those needs to have its own component
96 * master, because each different type of component needs to be bind to
97 * the connector independently of the other components. That requires
98 * improvements to the component framework. Right now you can only have
99 * one master per device.
100 */
101 return component_master_add_with_match(&con->dev, &typec_aggregate_ops, arg.match);
102 }
103
typec_unlink_ports(struct typec_port * con)104 void typec_unlink_ports(struct typec_port *con)
105 {
106 if (has_acpi_companion(&con->dev))
107 component_master_del(&con->dev, &typec_aggregate_ops);
108 }
109