1
2
3
4
5
6
7
8
9#include <linux/acpi.h>
10#include <linux/usb.h>
11#include <linux/usb/typec.h>
12
13#include "class.h"
14
15struct port_node {
16 struct list_head list;
17 struct device *dev;
18 void *pld;
19};
20
21static 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
29
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
37static 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
56static void free_pld(void *pld)
57{
58#ifdef CONFIG_ACPI
59 ACPI_FREE(pld);
60#endif
61}
62
63static 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
83static 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
94static 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
101static 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
108static 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
122static void remove_port_node(struct port_node *node)
123{
124 put_device(node->dev);
125 free_pld(node->pld);
126 kfree(node);
127}
128
129static 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
139static 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
149
150
151
152
153
154
155
156int typec_link_port(struct device *port)
157{
158 struct device *connector;
159 struct port_node *node;
160 int ret;
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 ret = 0;
169 goto remove_node;
170 }
171
172 ret = link_port(to_typec_port(connector), node);
173 if (ret)
174 goto put_connector;
175
176 return 0;
177
178put_connector:
179 put_device(connector);
180remove_node:
181 remove_port_node(node);
182
183 return ret;
184}
185EXPORT_SYMBOL_GPL(typec_link_port);
186
187static int port_match_and_unlink(struct device *connector, void *port)
188{
189 struct port_node *node;
190 struct port_node *tmp;
191 int ret = 0;
192
193 if (!is_typec_port(connector))
194 return 0;
195
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;
199 if (ret) {
200 unlink_port(to_typec_port(connector), node);
201 remove_port_node(node);
202 put_device(connector);
203 break;
204 }
205 }
206 mutex_unlock(&to_typec_port(connector)->port_list_lock);
207
208 return ret;
209}
210
211
212
213
214
215
216
217void typec_unlink_port(struct device *port)
218{
219 class_for_each_device(&typec_class, NULL, port, port_match_and_unlink);
220}
221EXPORT_SYMBOL_GPL(typec_unlink_port);
222
223static int each_port(struct device *port, void *connector)
224{
225 struct port_node *node;
226 int ret;
227
228 node = create_port_node(port);
229 if (IS_ERR(node))
230 return PTR_ERR(node);
231
232 if (!connector_match(connector, node)) {
233 remove_port_node(node);
234 return 0;
235 }
236
237 ret = link_port(to_typec_port(connector), node);
238 if (ret) {
239 remove_port_node(node->pld);
240 return ret;
241 }
242
243 get_device(connector);
244
245 return 0;
246}
247
248int typec_link_ports(struct typec_port *con)
249{
250 int ret = 0;
251
252 con->pld = get_pld(&con->dev);
253 if (!con->pld)
254 return 0;
255
256 ret = usb_for_each_port(&con->dev, each_port);
257 if (ret)
258 typec_unlink_ports(con);
259
260 return ret;
261}
262
263void typec_unlink_ports(struct typec_port *con)
264{
265 struct port_node *node;
266 struct port_node *tmp;
267
268 mutex_lock(&con->port_list_lock);
269
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);
274 }
275
276 mutex_unlock(&con->port_list_lock);
277
278 free_pld(con->pld);
279}
280