xref: /linux-6.15/drivers/usb/typec/bus.c (revision 8a37d87d)
1 // SPDX-License-Identifier: GPL-2.0
2 /**
3  * Bus for USB Type-C Alternate Modes
4  *
5  * Copyright (C) 2018 Intel Corporation
6  * Author: Heikki Krogerus <[email protected]>
7  */
8 
9 #include <linux/usb/pd_vdo.h>
10 
11 #include "bus.h"
12 
13 static inline int typec_altmode_set_mux(struct altmode *alt, u8 state)
14 {
15 	return alt->mux ? alt->mux->set(alt->mux, state) : 0;
16 }
17 
18 static int typec_altmode_set_state(struct typec_altmode *adev, int state)
19 {
20 	bool is_port = is_typec_port(adev->dev.parent);
21 	struct altmode *port_altmode;
22 	int ret;
23 
24 	port_altmode = is_port ? to_altmode(adev) : to_altmode(adev)->partner;
25 
26 	ret = typec_altmode_set_mux(port_altmode, state);
27 	if (ret)
28 		return ret;
29 
30 	blocking_notifier_call_chain(&port_altmode->nh, state, NULL);
31 
32 	return 0;
33 }
34 
35 /* -------------------------------------------------------------------------- */
36 /* Common API */
37 
38 /**
39  * typec_altmode_notify - Communication between the OS and alternate mode driver
40  * @adev: Handle to the alternate mode
41  * @conf: Alternate mode specific configuration value
42  * @data: Alternate mode specific data
43  *
44  * The primary purpose for this function is to allow the alternate mode drivers
45  * to tell which pin configuration has been negotiated with the partner. That
46  * information will then be used for example to configure the muxes.
47  * Communication to the other direction is also possible, and low level device
48  * drivers can also send notifications to the alternate mode drivers. The actual
49  * communication will be specific for every SVID.
50  */
51 int typec_altmode_notify(struct typec_altmode *adev,
52 			 unsigned long conf, void *data)
53 {
54 	bool is_port = is_typec_port(adev->dev.parent);
55 	struct altmode *altmode;
56 	struct altmode *partner;
57 	int ret;
58 
59 	if (!adev)
60 		return 0;
61 
62 	altmode = to_altmode(adev);
63 
64 	if (!altmode->partner)
65 		return -ENODEV;
66 
67 	partner = altmode->partner;
68 
69 	ret = typec_altmode_set_mux(is_port ? altmode : partner, (u8)conf);
70 	if (ret)
71 		return ret;
72 
73 	blocking_notifier_call_chain(is_port ? &altmode->nh : &partner->nh,
74 				     conf, data);
75 
76 	if (partner->adev.ops && partner->adev.ops->notify)
77 		return partner->adev.ops->notify(&partner->adev, conf, data);
78 
79 	return 0;
80 }
81 EXPORT_SYMBOL_GPL(typec_altmode_notify);
82 
83 /**
84  * typec_altmode_enter - Enter Mode
85  * @adev: The alternate mode
86  *
87  * The alternate mode drivers use this function to enter mode. The port drivers
88  * use this to inform the alternate mode drivers that the partner has initiated
89  * Enter Mode command.
90  */
91 int typec_altmode_enter(struct typec_altmode *adev)
92 {
93 	struct altmode *partner = to_altmode(adev)->partner;
94 	struct typec_altmode *pdev = &partner->adev;
95 	int ret;
96 
97 	if (!adev || adev->active)
98 		return 0;
99 
100 	if (!pdev->ops || !pdev->ops->enter)
101 		return -EOPNOTSUPP;
102 
103 	/* Moving to USB Safe State */
104 	ret = typec_altmode_set_state(adev, TYPEC_STATE_SAFE);
105 	if (ret)
106 		return ret;
107 
108 	/* Enter Mode */
109 	return pdev->ops->enter(pdev);
110 }
111 EXPORT_SYMBOL_GPL(typec_altmode_enter);
112 
113 /**
114  * typec_altmode_exit - Exit Mode
115  * @adev: The alternate mode
116  *
117  * The partner of @adev has initiated Exit Mode command.
118  */
119 int typec_altmode_exit(struct typec_altmode *adev)
120 {
121 	struct altmode *partner = to_altmode(adev)->partner;
122 	struct typec_altmode *pdev = &partner->adev;
123 	int ret;
124 
125 	if (!adev || !adev->active)
126 		return 0;
127 
128 	if (!pdev->ops || !pdev->ops->enter)
129 		return -EOPNOTSUPP;
130 
131 	/* Moving to USB Safe State */
132 	ret = typec_altmode_set_state(adev, TYPEC_STATE_SAFE);
133 	if (ret)
134 		return ret;
135 
136 	/* Exit Mode command */
137 	return pdev->ops->exit(pdev);
138 }
139 EXPORT_SYMBOL_GPL(typec_altmode_exit);
140 
141 /**
142  * typec_altmode_attention - Attention command
143  * @adev: The alternate mode
144  * @vdo: VDO for the Attention command
145  *
146  * Notifies the partner of @adev about Attention command.
147  */
148 void typec_altmode_attention(struct typec_altmode *adev, u32 vdo)
149 {
150 	struct typec_altmode *pdev = &to_altmode(adev)->partner->adev;
151 
152 	if (pdev->ops && pdev->ops->attention)
153 		pdev->ops->attention(pdev, vdo);
154 }
155 EXPORT_SYMBOL_GPL(typec_altmode_attention);
156 
157 /**
158  * typec_altmode_vdm - Send Vendor Defined Messages (VDM) to the partner
159  * @adev: Alternate mode handle
160  * @header: VDM Header
161  * @vdo: Array of Vendor Defined Data Objects
162  * @count: Number of Data Objects
163  *
164  * The alternate mode drivers use this function for SVID specific communication
165  * with the partner. The port drivers use it to deliver the Structured VDMs
166  * received from the partners to the alternate mode drivers.
167  */
168 int typec_altmode_vdm(struct typec_altmode *adev,
169 		      const u32 header, const u32 *vdo, int count)
170 {
171 	struct typec_altmode *pdev;
172 	struct altmode *altmode;
173 
174 	if (!adev)
175 		return 0;
176 
177 	altmode = to_altmode(adev);
178 
179 	if (!altmode->partner)
180 		return -ENODEV;
181 
182 	pdev = &altmode->partner->adev;
183 
184 	if (!pdev->ops || !pdev->ops->vdm)
185 		return -EOPNOTSUPP;
186 
187 	return pdev->ops->vdm(pdev, header, vdo, count);
188 }
189 EXPORT_SYMBOL_GPL(typec_altmode_vdm);
190 
191 const struct typec_altmode *
192 typec_altmode_get_partner(struct typec_altmode *adev)
193 {
194 	return &to_altmode(adev)->partner->adev;
195 }
196 EXPORT_SYMBOL_GPL(typec_altmode_get_partner);
197 
198 /* -------------------------------------------------------------------------- */
199 /* API for the alternate mode drivers */
200 
201 /**
202  * typec_altmode_get_plug - Find cable plug alternate mode
203  * @adev: Handle to partner alternate mode
204  * @index: Cable plug index
205  *
206  * Increment reference count for cable plug alternate mode device. Returns
207  * handle to the cable plug alternate mode, or NULL if none is found.
208  */
209 struct typec_altmode *typec_altmode_get_plug(struct typec_altmode *adev,
210 					     enum typec_plug_index index)
211 {
212 	struct altmode *port = to_altmode(adev)->partner;
213 
214 	if (port->plug[index]) {
215 		get_device(&port->plug[index]->adev.dev);
216 		return &port->plug[index]->adev;
217 	}
218 
219 	return NULL;
220 }
221 EXPORT_SYMBOL_GPL(typec_altmode_get_plug);
222 
223 /**
224  * typec_altmode_put_plug - Decrement cable plug alternate mode reference count
225  * @plug: Handle to the cable plug alternate mode
226  */
227 void typec_altmode_put_plug(struct typec_altmode *plug)
228 {
229 	if (plug)
230 		put_device(&plug->dev);
231 }
232 EXPORT_SYMBOL_GPL(typec_altmode_put_plug);
233 
234 int __typec_altmode_register_driver(struct typec_altmode_driver *drv,
235 				    struct module *module)
236 {
237 	if (!drv->probe)
238 		return -EINVAL;
239 
240 	drv->driver.owner = module;
241 	drv->driver.bus = &typec_bus;
242 
243 	return driver_register(&drv->driver);
244 }
245 EXPORT_SYMBOL_GPL(__typec_altmode_register_driver);
246 
247 void typec_altmode_unregister_driver(struct typec_altmode_driver *drv)
248 {
249 	driver_unregister(&drv->driver);
250 }
251 EXPORT_SYMBOL_GPL(typec_altmode_unregister_driver);
252 
253 /* -------------------------------------------------------------------------- */
254 /* API for the port drivers */
255 
256 /**
257  * typec_match_altmode - Match SVID to an array of alternate modes
258  * @altmodes: Array of alternate modes
259  * @n: Number of elements in the array, or -1 for NULL termiated arrays
260  * @svid: Standard or Vendor ID to match with
261  *
262  * Return pointer to an alternate mode with SVID mathing @svid, or NULL when no
263  * match is found.
264  */
265 struct typec_altmode *typec_match_altmode(struct typec_altmode **altmodes,
266 					  size_t n, u16 svid, u8 mode)
267 {
268 	int i;
269 
270 	for (i = 0; i < n; i++) {
271 		if (!altmodes[i])
272 			break;
273 		if (altmodes[i]->svid == svid && altmodes[i]->mode == mode)
274 			return altmodes[i];
275 	}
276 
277 	return NULL;
278 }
279 EXPORT_SYMBOL_GPL(typec_match_altmode);
280 
281 /* -------------------------------------------------------------------------- */
282 
283 static ssize_t
284 description_show(struct device *dev, struct device_attribute *attr, char *buf)
285 {
286 	struct typec_altmode *alt = to_typec_altmode(dev);
287 
288 	return sprintf(buf, "%s\n", alt->desc ? alt->desc : "");
289 }
290 static DEVICE_ATTR_RO(description);
291 
292 static struct attribute *typec_attrs[] = {
293 	&dev_attr_description.attr,
294 	NULL
295 };
296 ATTRIBUTE_GROUPS(typec);
297 
298 static int typec_match(struct device *dev, struct device_driver *driver)
299 {
300 	struct typec_altmode_driver *drv = to_altmode_driver(driver);
301 	struct typec_altmode *altmode = to_typec_altmode(dev);
302 	const struct typec_device_id *id;
303 
304 	for (id = drv->id_table; id->svid; id++)
305 		if (id->svid == altmode->svid &&
306 		    (id->mode == TYPEC_ANY_MODE || id->mode == altmode->mode))
307 			return 1;
308 	return 0;
309 }
310 
311 static int typec_uevent(struct device *dev, struct kobj_uevent_env *env)
312 {
313 	struct typec_altmode *altmode = to_typec_altmode(dev);
314 
315 	if (add_uevent_var(env, "SVID=%04X", altmode->svid))
316 		return -ENOMEM;
317 
318 	if (add_uevent_var(env, "MODE=%u", altmode->mode))
319 		return -ENOMEM;
320 
321 	return add_uevent_var(env, "MODALIAS=typec:id%04Xm%02X",
322 			      altmode->svid, altmode->mode);
323 }
324 
325 static int typec_altmode_create_links(struct altmode *alt)
326 {
327 	struct device *port_dev = &alt->partner->adev.dev;
328 	struct device *dev = &alt->adev.dev;
329 	int err;
330 
331 	err = sysfs_create_link(&dev->kobj, &port_dev->kobj, "port");
332 	if (err)
333 		return err;
334 
335 	err = sysfs_create_link(&port_dev->kobj, &dev->kobj, "partner");
336 	if (err)
337 		sysfs_remove_link(&dev->kobj, "port");
338 
339 	return err;
340 }
341 
342 static void typec_altmode_remove_links(struct altmode *alt)
343 {
344 	sysfs_remove_link(&alt->partner->adev.dev.kobj, "partner");
345 	sysfs_remove_link(&alt->adev.dev.kobj, "port");
346 }
347 
348 static int typec_probe(struct device *dev)
349 {
350 	struct typec_altmode_driver *drv = to_altmode_driver(dev->driver);
351 	struct typec_altmode *adev = to_typec_altmode(dev);
352 	struct altmode *altmode = to_altmode(adev);
353 	int ret;
354 
355 	/* Fail if the port does not support the alternate mode */
356 	if (!altmode->partner)
357 		return -ENODEV;
358 
359 	ret = typec_altmode_create_links(altmode);
360 	if (ret) {
361 		dev_warn(dev, "failed to create symlinks\n");
362 		return ret;
363 	}
364 
365 	ret = drv->probe(adev);
366 	if (ret)
367 		typec_altmode_remove_links(altmode);
368 
369 	return ret;
370 }
371 
372 static int typec_remove(struct device *dev)
373 {
374 	struct typec_altmode_driver *drv = to_altmode_driver(dev->driver);
375 	struct typec_altmode *adev = to_typec_altmode(dev);
376 	struct altmode *altmode = to_altmode(adev);
377 
378 	typec_altmode_remove_links(altmode);
379 
380 	if (drv->remove)
381 		drv->remove(to_typec_altmode(dev));
382 
383 	if (adev->active) {
384 		WARN_ON(typec_altmode_set_state(adev, TYPEC_STATE_SAFE));
385 		typec_altmode_update_active(adev, false);
386 	}
387 
388 	adev->desc = NULL;
389 	adev->ops = NULL;
390 
391 	return 0;
392 }
393 
394 struct bus_type typec_bus = {
395 	.name = "typec",
396 	.dev_groups = typec_groups,
397 	.match = typec_match,
398 	.uevent = typec_uevent,
399 	.probe = typec_probe,
400 	.remove = typec_remove,
401 };
402