1 // SPDX-License-Identifier: GPL-2.0
3 * USB Type-C Multiplexer/DeMultiplexer Switch support
5 * Copyright (C) 2018 Intel Corporation
6 * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
7 * Hans de Goede <hdegoede@redhat.com>
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 #include <linux/usb/typec_mux.h>
20 static int name_match(struct device
*dev
, const void *name
)
22 return !strcmp((const char *)name
, dev_name(dev
));
25 static bool dev_name_ends_with(struct device
*dev
, const char *suffix
)
27 const char *name
= dev_name(dev
);
28 const int name_len
= strlen(name
);
29 const int suffix_len
= strlen(suffix
);
31 if (suffix_len
> name_len
)
34 return strcmp(name
+ (name_len
- suffix_len
), suffix
) == 0;
37 static int switch_fwnode_match(struct device
*dev
, const void *fwnode
)
39 return dev_fwnode(dev
) == fwnode
&& dev_name_ends_with(dev
, "-switch");
42 static void *typec_switch_match(struct device_connection
*con
, int ep
,
48 if (con
->id
&& !fwnode_property_present(con
->fwnode
, con
->id
))
51 dev
= class_find_device(&typec_mux_class
, NULL
, con
->fwnode
,
54 dev
= class_find_device(&typec_mux_class
, NULL
,
55 con
->endpoint
[ep
], name_match
);
58 return dev
? to_typec_switch(dev
) : ERR_PTR(-EPROBE_DEFER
);
62 * typec_switch_get - Find USB Type-C orientation switch
63 * @dev: The caller device
65 * Finds a switch linked with @dev. Returns a reference to the switch on
66 * success, NULL if no matching connection was found, or
67 * ERR_PTR(-EPROBE_DEFER) when a connection was found but the switch
68 * has not been enumerated yet.
70 struct typec_switch
*typec_switch_get(struct device
*dev
)
72 struct typec_switch
*sw
;
74 sw
= device_connection_find_match(dev
, "orientation-switch", NULL
,
76 if (!IS_ERR_OR_NULL(sw
))
77 WARN_ON(!try_module_get(sw
->dev
.parent
->driver
->owner
));
81 EXPORT_SYMBOL_GPL(typec_switch_get
);
84 * typec_put_switch - Release USB Type-C orientation switch
85 * @sw: USB Type-C orientation switch
87 * Decrement reference count for @sw.
89 void typec_switch_put(struct typec_switch
*sw
)
91 if (!IS_ERR_OR_NULL(sw
)) {
92 module_put(sw
->dev
.parent
->driver
->owner
);
96 EXPORT_SYMBOL_GPL(typec_switch_put
);
98 static void typec_switch_release(struct device
*dev
)
100 kfree(to_typec_switch(dev
));
103 static const struct device_type typec_switch_dev_type
= {
104 .name
= "orientation_switch",
105 .release
= typec_switch_release
,
109 * typec_switch_register - Register USB Type-C orientation switch
110 * @parent: Parent device
111 * @desc: Orientation switch description
113 * This function registers a switch that can be used for routing the correct
114 * data pairs depending on the cable plug orientation from the USB Type-C
115 * connector to the USB controllers. USB Type-C plugs can be inserted
116 * right-side-up or upside-down.
118 struct typec_switch
*
119 typec_switch_register(struct device
*parent
,
120 const struct typec_switch_desc
*desc
)
122 struct typec_switch
*sw
;
125 if (!desc
|| !desc
->set
)
126 return ERR_PTR(-EINVAL
);
128 sw
= kzalloc(sizeof(*sw
), GFP_KERNEL
);
130 return ERR_PTR(-ENOMEM
);
134 device_initialize(&sw
->dev
);
135 sw
->dev
.parent
= parent
;
136 sw
->dev
.fwnode
= desc
->fwnode
;
137 sw
->dev
.class = &typec_mux_class
;
138 sw
->dev
.type
= &typec_switch_dev_type
;
139 sw
->dev
.driver_data
= desc
->drvdata
;
140 dev_set_name(&sw
->dev
, "%s-switch", dev_name(parent
));
142 ret
= device_add(&sw
->dev
);
144 dev_err(parent
, "failed to register switch (%d)\n", ret
);
145 put_device(&sw
->dev
);
151 EXPORT_SYMBOL_GPL(typec_switch_register
);
154 * typec_switch_unregister - Unregister USB Type-C orientation switch
155 * @sw: USB Type-C orientation switch
157 * Unregister switch that was registered with typec_switch_register().
159 void typec_switch_unregister(struct typec_switch
*sw
)
161 if (!IS_ERR_OR_NULL(sw
))
162 device_unregister(&sw
->dev
);
164 EXPORT_SYMBOL_GPL(typec_switch_unregister
);
166 void typec_switch_set_drvdata(struct typec_switch
*sw
, void *data
)
168 dev_set_drvdata(&sw
->dev
, data
);
170 EXPORT_SYMBOL_GPL(typec_switch_set_drvdata
);
172 void *typec_switch_get_drvdata(struct typec_switch
*sw
)
174 return dev_get_drvdata(&sw
->dev
);
176 EXPORT_SYMBOL_GPL(typec_switch_get_drvdata
);
178 /* ------------------------------------------------------------------------- */
180 static int mux_fwnode_match(struct device
*dev
, const void *fwnode
)
182 return dev_fwnode(dev
) == fwnode
&& dev_name_ends_with(dev
, "-mux");
185 static void *typec_mux_match(struct device_connection
*con
, int ep
, void *data
)
187 const struct typec_altmode_desc
*desc
= data
;
195 dev
= class_find_device(&typec_mux_class
, NULL
,
196 con
->endpoint
[ep
], name_match
);
198 return dev
? to_typec_switch(dev
) : ERR_PTR(-EPROBE_DEFER
);
202 * Check has the identifier already been "consumed". If it
203 * has, no need to do any extra connection identification.
209 /* Accessory Mode muxes */
211 match
= fwnode_property_present(con
->fwnode
, "accessory");
217 /* Alternate Mode muxes */
218 nval
= fwnode_property_count_u16(con
->fwnode
, "svid");
222 val
= kcalloc(nval
, sizeof(*val
), GFP_KERNEL
);
224 return ERR_PTR(-ENOMEM
);
226 nval
= fwnode_property_read_u16_array(con
->fwnode
, "svid", val
, nval
);
229 return ERR_PTR(nval
);
232 for (i
= 0; i
< nval
; i
++) {
233 match
= val
[i
] == desc
->svid
;
243 dev
= class_find_device(&typec_mux_class
, NULL
, con
->fwnode
,
246 return dev
? to_typec_switch(dev
) : ERR_PTR(-EPROBE_DEFER
);
250 * typec_mux_get - Find USB Type-C Multiplexer
251 * @dev: The caller device
252 * @desc: Alt Mode description
254 * Finds a mux linked to the caller. This function is primarily meant for the
255 * Type-C drivers. Returns a reference to the mux on success, NULL if no
256 * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection
257 * was found but the mux has not been enumerated yet.
259 struct typec_mux
*typec_mux_get(struct device
*dev
,
260 const struct typec_altmode_desc
*desc
)
262 struct typec_mux
*mux
;
264 mux
= device_connection_find_match(dev
, "mode-switch", (void *)desc
,
266 if (!IS_ERR_OR_NULL(mux
))
267 WARN_ON(!try_module_get(mux
->dev
.parent
->driver
->owner
));
271 EXPORT_SYMBOL_GPL(typec_mux_get
);
274 * typec_mux_put - Release handle to a Multiplexer
275 * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
277 * Decrements reference count for @mux.
279 void typec_mux_put(struct typec_mux
*mux
)
281 if (!IS_ERR_OR_NULL(mux
)) {
282 module_put(mux
->dev
.parent
->driver
->owner
);
283 put_device(&mux
->dev
);
286 EXPORT_SYMBOL_GPL(typec_mux_put
);
288 static void typec_mux_release(struct device
*dev
)
290 kfree(to_typec_mux(dev
));
293 static const struct device_type typec_mux_dev_type
= {
294 .name
= "mode_switch",
295 .release
= typec_mux_release
,
299 * typec_mux_register - Register Multiplexer routing USB Type-C pins
300 * @parent: Parent device
301 * @desc: Multiplexer description
303 * USB Type-C connectors can be used for alternate modes of operation besides
304 * USB when Accessory/Alternate Modes are supported. With some of those modes,
305 * the pins on the connector need to be reconfigured. This function registers
306 * multiplexer switches routing the pins on the connector.
309 typec_mux_register(struct device
*parent
, const struct typec_mux_desc
*desc
)
311 struct typec_mux
*mux
;
314 if (!desc
|| !desc
->set
)
315 return ERR_PTR(-EINVAL
);
317 mux
= kzalloc(sizeof(*mux
), GFP_KERNEL
);
319 return ERR_PTR(-ENOMEM
);
321 mux
->set
= desc
->set
;
323 device_initialize(&mux
->dev
);
324 mux
->dev
.parent
= parent
;
325 mux
->dev
.fwnode
= desc
->fwnode
;
326 mux
->dev
.class = &typec_mux_class
;
327 mux
->dev
.type
= &typec_mux_dev_type
;
328 mux
->dev
.driver_data
= desc
->drvdata
;
329 dev_set_name(&mux
->dev
, "%s-mux", dev_name(parent
));
331 ret
= device_add(&mux
->dev
);
333 dev_err(parent
, "failed to register mux (%d)\n", ret
);
334 put_device(&mux
->dev
);
340 EXPORT_SYMBOL_GPL(typec_mux_register
);
343 * typec_mux_unregister - Unregister Multiplexer Switch
344 * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
346 * Unregister mux that was registered with typec_mux_register().
348 void typec_mux_unregister(struct typec_mux
*mux
)
350 if (!IS_ERR_OR_NULL(mux
))
351 device_unregister(&mux
->dev
);
353 EXPORT_SYMBOL_GPL(typec_mux_unregister
);
355 void typec_mux_set_drvdata(struct typec_mux
*mux
, void *data
)
357 dev_set_drvdata(&mux
->dev
, data
);
359 EXPORT_SYMBOL_GPL(typec_mux_set_drvdata
);
361 void *typec_mux_get_drvdata(struct typec_mux
*mux
)
363 return dev_get_drvdata(&mux
->dev
);
365 EXPORT_SYMBOL_GPL(typec_mux_get_drvdata
);
367 struct class typec_mux_class
= {
369 .owner
= THIS_MODULE
,