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>
20 static int switch_fwnode_match(struct device *dev, const void *fwnode)
22 if (!is_typec_switch(dev))
25 return dev_fwnode(dev) == fwnode;
28 static void *typec_switch_match(struct fwnode_handle *fwnode, const char *id,
34 * Device graph (OF graph) does not give any means to identify the
35 * device type or the device class of the remote port parent that @fwnode
36 * represents, so in order to identify the type or the class of @fwnode
37 * an additional device property is needed. With typec switches the
38 * property is named "orientation-switch" (@id). The value of the device
39 * property is ignored.
41 if (id && !fwnode_property_present(fwnode, id))
45 * At this point we are sure that @fwnode is a typec switch in all
46 * cases. If the switch hasn't yet been registered for some reason, the
47 * function "defers probe" for now.
49 dev = class_find_device(&typec_mux_class, NULL, fwnode,
52 return dev ? to_typec_switch(dev) : ERR_PTR(-EPROBE_DEFER);
56 * fwnode_typec_switch_get - Find USB Type-C orientation switch
57 * @fwnode: The caller device node
59 * Finds a switch linked with @dev. Returns a reference to the switch on
60 * success, NULL if no matching connection was found, or
61 * ERR_PTR(-EPROBE_DEFER) when a connection was found but the switch
62 * has not been enumerated yet.
64 struct typec_switch *fwnode_typec_switch_get(struct fwnode_handle *fwnode)
66 struct typec_switch *sw;
68 sw = fwnode_connection_find_match(fwnode, "orientation-switch", NULL,
70 if (!IS_ERR_OR_NULL(sw))
71 WARN_ON(!try_module_get(sw->dev.parent->driver->owner));
75 EXPORT_SYMBOL_GPL(fwnode_typec_switch_get);
78 * typec_switch_put - Release USB Type-C orientation switch
79 * @sw: USB Type-C orientation switch
81 * Decrement reference count for @sw.
83 void typec_switch_put(struct typec_switch *sw)
85 if (!IS_ERR_OR_NULL(sw)) {
86 module_put(sw->dev.parent->driver->owner);
90 EXPORT_SYMBOL_GPL(typec_switch_put);
92 static void typec_switch_release(struct device *dev)
94 kfree(to_typec_switch(dev));
97 const struct device_type typec_switch_dev_type = {
98 .name = "orientation_switch",
99 .release = typec_switch_release,
103 * typec_switch_register - Register USB Type-C orientation switch
104 * @parent: Parent device
105 * @desc: Orientation switch description
107 * This function registers a switch that can be used for routing the correct
108 * data pairs depending on the cable plug orientation from the USB Type-C
109 * connector to the USB controllers. USB Type-C plugs can be inserted
110 * right-side-up or upside-down.
112 struct typec_switch *
113 typec_switch_register(struct device *parent,
114 const struct typec_switch_desc *desc)
116 struct typec_switch *sw;
119 if (!desc || !desc->set)
120 return ERR_PTR(-EINVAL);
122 sw = kzalloc(sizeof(*sw), GFP_KERNEL);
124 return ERR_PTR(-ENOMEM);
128 device_initialize(&sw->dev);
129 sw->dev.parent = parent;
130 sw->dev.fwnode = desc->fwnode;
131 sw->dev.class = &typec_mux_class;
132 sw->dev.type = &typec_switch_dev_type;
133 sw->dev.driver_data = desc->drvdata;
134 ret = dev_set_name(&sw->dev, "%s-switch", desc->name ? desc->name : dev_name(parent));
136 put_device(&sw->dev);
140 ret = device_add(&sw->dev);
142 dev_err(parent, "failed to register switch (%d)\n", ret);
143 put_device(&sw->dev);
149 EXPORT_SYMBOL_GPL(typec_switch_register);
151 int typec_switch_set(struct typec_switch *sw,
152 enum typec_orientation orientation)
154 if (IS_ERR_OR_NULL(sw))
157 return sw->set(sw, orientation);
159 EXPORT_SYMBOL_GPL(typec_switch_set);
162 * typec_switch_unregister - Unregister USB Type-C orientation switch
163 * @sw: USB Type-C orientation switch
165 * Unregister switch that was registered with typec_switch_register().
167 void typec_switch_unregister(struct typec_switch *sw)
169 if (!IS_ERR_OR_NULL(sw))
170 device_unregister(&sw->dev);
172 EXPORT_SYMBOL_GPL(typec_switch_unregister);
174 void typec_switch_set_drvdata(struct typec_switch *sw, void *data)
176 dev_set_drvdata(&sw->dev, data);
178 EXPORT_SYMBOL_GPL(typec_switch_set_drvdata);
180 void *typec_switch_get_drvdata(struct typec_switch *sw)
182 return dev_get_drvdata(&sw->dev);
184 EXPORT_SYMBOL_GPL(typec_switch_get_drvdata);
186 /* ------------------------------------------------------------------------- */
188 static int mux_fwnode_match(struct device *dev, const void *fwnode)
190 if (!is_typec_mux(dev))
193 return dev_fwnode(dev) == fwnode;
196 static void *typec_mux_match(struct fwnode_handle *fwnode, const char *id,
199 const struct typec_altmode_desc *desc = data;
208 * Check has the identifier already been "consumed". If it
209 * has, no need to do any extra connection identification.
215 /* Accessory Mode muxes */
217 match = fwnode_property_present(fwnode, "accessory");
223 /* Alternate Mode muxes */
224 nval = fwnode_property_count_u16(fwnode, "svid");
228 val = kcalloc(nval, sizeof(*val), GFP_KERNEL);
230 return ERR_PTR(-ENOMEM);
232 ret = fwnode_property_read_u16_array(fwnode, "svid", val, nval);
238 for (i = 0; i < nval; i++) {
239 match = val[i] == desc->svid;
249 dev = class_find_device(&typec_mux_class, NULL, fwnode,
252 return dev ? to_typec_mux(dev) : ERR_PTR(-EPROBE_DEFER);
256 * fwnode_typec_mux_get - Find USB Type-C Multiplexer
257 * @fwnode: The caller device node
258 * @desc: Alt Mode description
260 * Finds a mux linked to the caller. This function is primarily meant for the
261 * Type-C drivers. Returns a reference to the mux on success, NULL if no
262 * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection
263 * was found but the mux has not been enumerated yet.
265 struct typec_mux *fwnode_typec_mux_get(struct fwnode_handle *fwnode,
266 const struct typec_altmode_desc *desc)
268 struct typec_mux *mux;
270 mux = fwnode_connection_find_match(fwnode, "mode-switch", (void *)desc,
272 if (!IS_ERR_OR_NULL(mux))
273 WARN_ON(!try_module_get(mux->dev.parent->driver->owner));
277 EXPORT_SYMBOL_GPL(fwnode_typec_mux_get);
280 * typec_mux_put - Release handle to a Multiplexer
281 * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
283 * Decrements reference count for @mux.
285 void typec_mux_put(struct typec_mux *mux)
287 if (!IS_ERR_OR_NULL(mux)) {
288 module_put(mux->dev.parent->driver->owner);
289 put_device(&mux->dev);
292 EXPORT_SYMBOL_GPL(typec_mux_put);
294 int typec_mux_set(struct typec_mux *mux, struct typec_mux_state *state)
296 if (IS_ERR_OR_NULL(mux))
299 return mux->set(mux, state);
301 EXPORT_SYMBOL_GPL(typec_mux_set);
303 static void typec_mux_release(struct device *dev)
305 kfree(to_typec_mux(dev));
308 const struct device_type typec_mux_dev_type = {
309 .name = "mode_switch",
310 .release = typec_mux_release,
314 * typec_mux_register - Register Multiplexer routing USB Type-C pins
315 * @parent: Parent device
316 * @desc: Multiplexer description
318 * USB Type-C connectors can be used for alternate modes of operation besides
319 * USB when Accessory/Alternate Modes are supported. With some of those modes,
320 * the pins on the connector need to be reconfigured. This function registers
321 * multiplexer switches routing the pins on the connector.
324 typec_mux_register(struct device *parent, const struct typec_mux_desc *desc)
326 struct typec_mux *mux;
329 if (!desc || !desc->set)
330 return ERR_PTR(-EINVAL);
332 mux = kzalloc(sizeof(*mux), GFP_KERNEL);
334 return ERR_PTR(-ENOMEM);
336 mux->set = desc->set;
338 device_initialize(&mux->dev);
339 mux->dev.parent = parent;
340 mux->dev.fwnode = desc->fwnode;
341 mux->dev.class = &typec_mux_class;
342 mux->dev.type = &typec_mux_dev_type;
343 mux->dev.driver_data = desc->drvdata;
344 ret = dev_set_name(&mux->dev, "%s-mux", desc->name ? desc->name : dev_name(parent));
346 put_device(&mux->dev);
350 ret = device_add(&mux->dev);
352 dev_err(parent, "failed to register mux (%d)\n", ret);
353 put_device(&mux->dev);
359 EXPORT_SYMBOL_GPL(typec_mux_register);
362 * typec_mux_unregister - Unregister Multiplexer Switch
363 * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
365 * Unregister mux that was registered with typec_mux_register().
367 void typec_mux_unregister(struct typec_mux *mux)
369 if (!IS_ERR_OR_NULL(mux))
370 device_unregister(&mux->dev);
372 EXPORT_SYMBOL_GPL(typec_mux_unregister);
374 void typec_mux_set_drvdata(struct typec_mux *mux, void *data)
376 dev_set_drvdata(&mux->dev, data);
378 EXPORT_SYMBOL_GPL(typec_mux_set_drvdata);
380 void *typec_mux_get_drvdata(struct typec_mux *mux)
382 return dev_get_drvdata(&mux->dev);
384 EXPORT_SYMBOL_GPL(typec_mux_get_drvdata);
386 struct class typec_mux_class = {
388 .owner = THIS_MODULE,