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