Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1// SPDX-License-Identifier: GPL-2.0
2/*
3 * USB Type-C Multiplexer/DeMultiplexer Switch support
4 *
5 * Copyright (C) 2018 Intel Corporation
6 * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
7 * Hans de Goede <hdegoede@redhat.com>
8 */
9
10#include <linux/device.h>
11#include <linux/list.h>
12#include <linux/module.h>
13#include <linux/mutex.h>
14#include <linux/property.h>
15#include <linux/slab.h>
16
17#include "class.h"
18#include "mux.h"
19
20#define TYPEC_MUX_MAX_DEVS 3
21
22struct typec_switch {
23 struct typec_switch_dev *sw_devs[TYPEC_MUX_MAX_DEVS];
24 unsigned int num_sw_devs;
25};
26
27static int switch_fwnode_match(struct device *dev, const void *fwnode)
28{
29 if (!is_typec_switch_dev(dev))
30 return 0;
31
32 return device_match_fwnode(dev, fwnode);
33}
34
35static void *typec_switch_match(const struct fwnode_handle *fwnode,
36 const char *id, void *data)
37{
38 struct typec_switch_dev **sw_devs = data;
39 struct device *dev;
40 int i;
41
42 /*
43 * Device graph (OF graph) does not give any means to identify the
44 * device type or the device class of the remote port parent that @fwnode
45 * represents, so in order to identify the type or the class of @fwnode
46 * an additional device property is needed. With typec switches the
47 * property is named "orientation-switch" (@id). The value of the device
48 * property is ignored.
49 */
50 if (id && !fwnode_property_present(fwnode, id))
51 return NULL;
52
53 /*
54 * At this point we are sure that @fwnode is a typec switch in all
55 * cases. If the switch hasn't yet been registered for some reason, the
56 * function "defers probe" for now.
57 */
58 dev = class_find_device(&typec_mux_class, NULL, fwnode,
59 switch_fwnode_match);
60
61 /* Skip duplicates */
62 for (i = 0; i < TYPEC_MUX_MAX_DEVS; i++)
63 if (to_typec_switch_dev(dev) == sw_devs[i]) {
64 put_device(dev);
65 return NULL;
66 }
67
68 return dev ? to_typec_switch_dev(dev) : ERR_PTR(-EPROBE_DEFER);
69}
70
71/**
72 * fwnode_typec_switch_get - Find USB Type-C orientation switch
73 * @fwnode: The caller device node
74 *
75 * Finds a switch linked with @dev. Returns a reference to the switch on
76 * success, NULL if no matching connection was found, or
77 * ERR_PTR(-EPROBE_DEFER) when a connection was found but the switch
78 * has not been enumerated yet.
79 */
80struct typec_switch *fwnode_typec_switch_get(struct fwnode_handle *fwnode)
81{
82 struct typec_switch_dev *sw_devs[TYPEC_MUX_MAX_DEVS];
83 struct typec_switch *sw;
84 int count;
85 int err;
86 int i;
87
88 sw = kzalloc_obj(*sw);
89 if (!sw)
90 return ERR_PTR(-ENOMEM);
91
92 count = fwnode_connection_find_matches(fwnode, "orientation-switch",
93 (void **)sw_devs,
94 typec_switch_match,
95 (void **)sw_devs,
96 ARRAY_SIZE(sw_devs));
97 if (count <= 0) {
98 kfree(sw);
99 return NULL;
100 }
101
102 for (i = 0; i < count; i++) {
103 if (IS_ERR(sw_devs[i])) {
104 err = PTR_ERR(sw_devs[i]);
105 goto put_sw_devs;
106 }
107 }
108
109 for (i = 0; i < count; i++) {
110 WARN_ON(!try_module_get(sw_devs[i]->dev.parent->driver->owner));
111 sw->sw_devs[i] = sw_devs[i];
112 }
113
114 sw->num_sw_devs = count;
115
116 return sw;
117
118put_sw_devs:
119 for (i = 0; i < count; i++) {
120 if (!IS_ERR(sw_devs[i]))
121 put_device(&sw_devs[i]->dev);
122 }
123
124 kfree(sw);
125
126 return ERR_PTR(err);
127}
128EXPORT_SYMBOL_GPL(fwnode_typec_switch_get);
129
130/**
131 * typec_switch_put - Release USB Type-C orientation switch
132 * @sw: USB Type-C orientation switch
133 *
134 * Decrement reference count for @sw.
135 */
136void typec_switch_put(struct typec_switch *sw)
137{
138 struct typec_switch_dev *sw_dev;
139 unsigned int i;
140
141 if (IS_ERR_OR_NULL(sw))
142 return;
143
144 for (i = 0; i < sw->num_sw_devs; i++) {
145 sw_dev = sw->sw_devs[i];
146
147 module_put(sw_dev->dev.parent->driver->owner);
148 put_device(&sw_dev->dev);
149 }
150 kfree(sw);
151}
152EXPORT_SYMBOL_GPL(typec_switch_put);
153
154static void typec_switch_release(struct device *dev)
155{
156 kfree(to_typec_switch_dev(dev));
157}
158
159const struct device_type typec_switch_dev_type = {
160 .name = "orientation_switch",
161 .release = typec_switch_release,
162};
163
164/**
165 * typec_switch_register - Register USB Type-C orientation switch
166 * @parent: Parent device
167 * @desc: Orientation switch description
168 *
169 * This function registers a switch that can be used for routing the correct
170 * data pairs depending on the cable plug orientation from the USB Type-C
171 * connector to the USB controllers. USB Type-C plugs can be inserted
172 * right-side-up or upside-down.
173 */
174struct typec_switch_dev *
175typec_switch_register(struct device *parent,
176 const struct typec_switch_desc *desc)
177{
178 struct typec_switch_dev *sw_dev;
179 int ret;
180
181 if (!desc || !desc->set)
182 return ERR_PTR(-EINVAL);
183
184 sw_dev = kzalloc_obj(*sw_dev);
185 if (!sw_dev)
186 return ERR_PTR(-ENOMEM);
187
188 sw_dev->set = desc->set;
189
190 device_initialize(&sw_dev->dev);
191 sw_dev->dev.parent = parent;
192 sw_dev->dev.fwnode = desc->fwnode;
193 sw_dev->dev.class = &typec_mux_class;
194 sw_dev->dev.type = &typec_switch_dev_type;
195 sw_dev->dev.driver_data = desc->drvdata;
196 ret = dev_set_name(&sw_dev->dev, "%s-switch", desc->name ? desc->name : dev_name(parent));
197 if (ret) {
198 put_device(&sw_dev->dev);
199 return ERR_PTR(ret);
200 }
201
202 ret = device_add(&sw_dev->dev);
203 if (ret) {
204 dev_err(parent, "failed to register switch (%d)\n", ret);
205 put_device(&sw_dev->dev);
206 return ERR_PTR(ret);
207 }
208
209 return sw_dev;
210}
211EXPORT_SYMBOL_GPL(typec_switch_register);
212
213int typec_switch_set(struct typec_switch *sw,
214 enum typec_orientation orientation)
215{
216 struct typec_switch_dev *sw_dev;
217 unsigned int i;
218 int ret;
219
220 if (IS_ERR_OR_NULL(sw))
221 return 0;
222
223 for (i = 0; i < sw->num_sw_devs; i++) {
224 sw_dev = sw->sw_devs[i];
225
226 ret = sw_dev->set(sw_dev, orientation);
227 if (ret && ret != -EOPNOTSUPP)
228 return ret;
229 }
230
231 return 0;
232}
233EXPORT_SYMBOL_GPL(typec_switch_set);
234
235/**
236 * typec_switch_unregister - Unregister USB Type-C orientation switch
237 * @sw_dev: USB Type-C orientation switch
238 *
239 * Unregister switch that was registered with typec_switch_register().
240 */
241void typec_switch_unregister(struct typec_switch_dev *sw_dev)
242{
243 if (!IS_ERR_OR_NULL(sw_dev))
244 device_unregister(&sw_dev->dev);
245}
246EXPORT_SYMBOL_GPL(typec_switch_unregister);
247
248void typec_switch_set_drvdata(struct typec_switch_dev *sw_dev, void *data)
249{
250 dev_set_drvdata(&sw_dev->dev, data);
251}
252EXPORT_SYMBOL_GPL(typec_switch_set_drvdata);
253
254void *typec_switch_get_drvdata(struct typec_switch_dev *sw_dev)
255{
256 return dev_get_drvdata(&sw_dev->dev);
257}
258EXPORT_SYMBOL_GPL(typec_switch_get_drvdata);
259
260/* ------------------------------------------------------------------------- */
261
262struct typec_mux {
263 struct typec_mux_dev *mux_devs[TYPEC_MUX_MAX_DEVS];
264 unsigned int num_mux_devs;
265};
266
267static int mux_fwnode_match(struct device *dev, const void *fwnode)
268{
269 if (!is_typec_mux_dev(dev))
270 return 0;
271
272 return device_match_fwnode(dev, fwnode);
273}
274
275static void *typec_mux_match(const struct fwnode_handle *fwnode,
276 const char *id, void *data)
277{
278 struct typec_mux_dev **mux_devs = data;
279 struct device *dev;
280 int i;
281
282 /*
283 * Device graph (OF graph) does not give any means to identify the
284 * device type or the device class of the remote port parent that @fwnode
285 * represents, so in order to identify the type or the class of @fwnode
286 * an additional device property is needed. With typec muxes the
287 * property is named "mode-switch" (@id). The value of the device
288 * property is ignored.
289 */
290 if (id && !fwnode_property_present(fwnode, id))
291 return NULL;
292
293 dev = class_find_device(&typec_mux_class, NULL, fwnode,
294 mux_fwnode_match);
295
296 /* Skip duplicates */
297 for (i = 0; i < TYPEC_MUX_MAX_DEVS; i++)
298 if (to_typec_mux_dev(dev) == mux_devs[i]) {
299 put_device(dev);
300 return NULL;
301 }
302
303
304 return dev ? to_typec_mux_dev(dev) : ERR_PTR(-EPROBE_DEFER);
305}
306
307/**
308 * fwnode_typec_mux_get - Find USB Type-C Multiplexer
309 * @fwnode: The caller device node
310 *
311 * Finds a mux linked to the caller. This function is primarily meant for the
312 * Type-C drivers. Returns a reference to the mux on success, NULL if no
313 * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection
314 * was found but the mux has not been enumerated yet.
315 */
316struct typec_mux *fwnode_typec_mux_get(struct fwnode_handle *fwnode)
317{
318 struct typec_mux_dev *mux_devs[TYPEC_MUX_MAX_DEVS];
319 struct typec_mux *mux;
320 int count;
321 int err;
322 int i;
323
324 mux = kzalloc_obj(*mux);
325 if (!mux)
326 return ERR_PTR(-ENOMEM);
327
328 count = fwnode_connection_find_matches(fwnode, "mode-switch",
329 (void **)mux_devs,
330 typec_mux_match,
331 (void **)mux_devs,
332 ARRAY_SIZE(mux_devs));
333 if (count <= 0) {
334 kfree(mux);
335 return NULL;
336 }
337
338 for (i = 0; i < count; i++) {
339 if (IS_ERR(mux_devs[i])) {
340 err = PTR_ERR(mux_devs[i]);
341 goto put_mux_devs;
342 }
343 }
344
345 for (i = 0; i < count; i++) {
346 WARN_ON(!try_module_get(mux_devs[i]->dev.parent->driver->owner));
347 mux->mux_devs[i] = mux_devs[i];
348 }
349
350 mux->num_mux_devs = count;
351
352 return mux;
353
354put_mux_devs:
355 for (i = 0; i < count; i++) {
356 if (!IS_ERR(mux_devs[i]))
357 put_device(&mux_devs[i]->dev);
358 }
359
360 kfree(mux);
361
362 return ERR_PTR(err);
363}
364EXPORT_SYMBOL_GPL(fwnode_typec_mux_get);
365
366/**
367 * typec_mux_put - Release handle to a Multiplexer
368 * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
369 *
370 * Decrements reference count for @mux.
371 */
372void typec_mux_put(struct typec_mux *mux)
373{
374 struct typec_mux_dev *mux_dev;
375 unsigned int i;
376
377 if (IS_ERR_OR_NULL(mux))
378 return;
379
380 for (i = 0; i < mux->num_mux_devs; i++) {
381 mux_dev = mux->mux_devs[i];
382 module_put(mux_dev->dev.parent->driver->owner);
383 put_device(&mux_dev->dev);
384 }
385 kfree(mux);
386}
387EXPORT_SYMBOL_GPL(typec_mux_put);
388
389int typec_mux_set(struct typec_mux *mux, struct typec_mux_state *state)
390{
391 struct typec_mux_dev *mux_dev;
392 unsigned int i;
393 int ret;
394
395 if (IS_ERR_OR_NULL(mux))
396 return 0;
397
398 for (i = 0; i < mux->num_mux_devs; i++) {
399 mux_dev = mux->mux_devs[i];
400
401 ret = mux_dev->set(mux_dev, state);
402 if (ret && ret != -EOPNOTSUPP)
403 return ret;
404 }
405
406 return 0;
407}
408EXPORT_SYMBOL_GPL(typec_mux_set);
409
410static void typec_mux_release(struct device *dev)
411{
412 kfree(to_typec_mux_dev(dev));
413}
414
415const struct device_type typec_mux_dev_type = {
416 .name = "mode_switch",
417 .release = typec_mux_release,
418};
419
420/**
421 * typec_mux_register - Register Multiplexer routing USB Type-C pins
422 * @parent: Parent device
423 * @desc: Multiplexer description
424 *
425 * USB Type-C connectors can be used for alternate modes of operation besides
426 * USB when Accessory/Alternate Modes are supported. With some of those modes,
427 * the pins on the connector need to be reconfigured. This function registers
428 * multiplexer switches routing the pins on the connector.
429 */
430struct typec_mux_dev *
431typec_mux_register(struct device *parent, const struct typec_mux_desc *desc)
432{
433 struct typec_mux_dev *mux_dev;
434 int ret;
435
436 if (!desc || !desc->set)
437 return ERR_PTR(-EINVAL);
438
439 mux_dev = kzalloc_obj(*mux_dev);
440 if (!mux_dev)
441 return ERR_PTR(-ENOMEM);
442
443 mux_dev->set = desc->set;
444
445 device_initialize(&mux_dev->dev);
446 mux_dev->dev.parent = parent;
447 mux_dev->dev.fwnode = desc->fwnode;
448 mux_dev->dev.class = &typec_mux_class;
449 mux_dev->dev.type = &typec_mux_dev_type;
450 mux_dev->dev.driver_data = desc->drvdata;
451 ret = dev_set_name(&mux_dev->dev, "%s-mux", desc->name ? desc->name : dev_name(parent));
452 if (ret) {
453 put_device(&mux_dev->dev);
454 return ERR_PTR(ret);
455 }
456
457 ret = device_add(&mux_dev->dev);
458 if (ret) {
459 dev_err(parent, "failed to register mux (%d)\n", ret);
460 put_device(&mux_dev->dev);
461 return ERR_PTR(ret);
462 }
463
464 return mux_dev;
465}
466EXPORT_SYMBOL_GPL(typec_mux_register);
467
468/**
469 * typec_mux_unregister - Unregister Multiplexer Switch
470 * @mux_dev: USB Type-C Connector Multiplexer/DeMultiplexer
471 *
472 * Unregister mux that was registered with typec_mux_register().
473 */
474void typec_mux_unregister(struct typec_mux_dev *mux_dev)
475{
476 if (!IS_ERR_OR_NULL(mux_dev))
477 device_unregister(&mux_dev->dev);
478}
479EXPORT_SYMBOL_GPL(typec_mux_unregister);
480
481void typec_mux_set_drvdata(struct typec_mux_dev *mux_dev, void *data)
482{
483 dev_set_drvdata(&mux_dev->dev, data);
484}
485EXPORT_SYMBOL_GPL(typec_mux_set_drvdata);
486
487void *typec_mux_get_drvdata(struct typec_mux_dev *mux_dev)
488{
489 return dev_get_drvdata(&mux_dev->dev);
490}
491EXPORT_SYMBOL_GPL(typec_mux_get_drvdata);
492
493const struct class typec_mux_class = {
494 .name = "typec_mux",
495};