xref: /openbmc/linux/drivers/usb/typec/mux.c (revision 3524fe31)
1bdecb33aSHeikki Krogerus // SPDX-License-Identifier: GPL-2.0
22d686c73SRandy Dunlap /*
3bdecb33aSHeikki Krogerus  * USB Type-C Multiplexer/DeMultiplexer Switch support
4bdecb33aSHeikki Krogerus  *
5bdecb33aSHeikki Krogerus  * Copyright (C) 2018 Intel Corporation
6bdecb33aSHeikki Krogerus  * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
7bdecb33aSHeikki Krogerus  *         Hans de Goede <hdegoede@redhat.com>
8bdecb33aSHeikki Krogerus  */
9bdecb33aSHeikki Krogerus 
10bdecb33aSHeikki Krogerus #include <linux/device.h>
11bdecb33aSHeikki Krogerus #include <linux/list.h>
123e3b8196SHeikki Krogerus #include <linux/module.h>
13bdecb33aSHeikki Krogerus #include <linux/mutex.h>
1496a6d031SHeikki Krogerus #include <linux/property.h>
1596a6d031SHeikki Krogerus #include <linux/slab.h>
16bdecb33aSHeikki Krogerus 
171e2ed7b2SHeikki Krogerus #include "class.h"
181e2ed7b2SHeikki Krogerus #include "mux.h"
193370db35SHeikki Krogerus 
2071793b57SBjorn Andersson #define TYPEC_MUX_MAX_DEVS	3
2171793b57SBjorn Andersson 
22713fd49bSBjorn Andersson struct typec_switch {
2371793b57SBjorn Andersson 	struct typec_switch_dev *sw_devs[TYPEC_MUX_MAX_DEVS];
2471793b57SBjorn Andersson 	unsigned int num_sw_devs;
25713fd49bSBjorn Andersson };
26713fd49bSBjorn Andersson 
switch_fwnode_match(struct device * dev,const void * fwnode)273370db35SHeikki Krogerus static int switch_fwnode_match(struct device *dev, const void *fwnode)
283370db35SHeikki Krogerus {
29713fd49bSBjorn Andersson 	if (!is_typec_switch_dev(dev))
307bf991eaSHeikki Krogerus 		return 0;
317bf991eaSHeikki Krogerus 
32c9180362SAndy Shevchenko 	return device_match_fwnode(dev, fwnode);
333370db35SHeikki Krogerus }
34bdecb33aSHeikki Krogerus 
typec_switch_match(const struct fwnode_handle * fwnode,const char * id,void * data)3523ead33bSAndy Shevchenko static void *typec_switch_match(const struct fwnode_handle *fwnode,
3623ead33bSAndy Shevchenko 				const char *id, void *data)
37bdecb33aSHeikki Krogerus {
383370db35SHeikki Krogerus 	struct device *dev;
39bdecb33aSHeikki Krogerus 
407f7d0afeSHeikki Krogerus 	/*
417f7d0afeSHeikki Krogerus 	 * Device graph (OF graph) does not give any means to identify the
427f7d0afeSHeikki Krogerus 	 * device type or the device class of the remote port parent that @fwnode
437f7d0afeSHeikki Krogerus 	 * represents, so in order to identify the type or the class of @fwnode
447f7d0afeSHeikki Krogerus 	 * an additional device property is needed. With typec switches the
457f7d0afeSHeikki Krogerus 	 * property is named "orientation-switch" (@id). The value of the device
467f7d0afeSHeikki Krogerus 	 * property is ignored.
477f7d0afeSHeikki Krogerus 	 */
487f7d0afeSHeikki Krogerus 	if (id && !fwnode_property_present(fwnode, id))
497f7d0afeSHeikki Krogerus 		return NULL;
507f7d0afeSHeikki Krogerus 
517f7d0afeSHeikki Krogerus 	/*
527f7d0afeSHeikki Krogerus 	 * At this point we are sure that @fwnode is a typec switch in all
537f7d0afeSHeikki Krogerus 	 * cases. If the switch hasn't yet been registered for some reason, the
547f7d0afeSHeikki Krogerus 	 * function "defers probe" for now.
557f7d0afeSHeikki Krogerus 	 */
56f5514c91SHeikki Krogerus 	dev = class_find_device(&typec_mux_class, NULL, fwnode,
573370db35SHeikki Krogerus 				switch_fwnode_match);
5896a6d031SHeikki Krogerus 
59713fd49bSBjorn Andersson 	return dev ? to_typec_switch_dev(dev) : ERR_PTR(-EPROBE_DEFER);
60bdecb33aSHeikki Krogerus }
61bdecb33aSHeikki Krogerus 
62bdecb33aSHeikki Krogerus /**
63d1c6a769SHeikki Krogerus  * fwnode_typec_switch_get - Find USB Type-C orientation switch
64d1c6a769SHeikki Krogerus  * @fwnode: The caller device node
65bdecb33aSHeikki Krogerus  *
66bdecb33aSHeikki Krogerus  * Finds a switch linked with @dev. Returns a reference to the switch on
67bdecb33aSHeikki Krogerus  * success, NULL if no matching connection was found, or
68bdecb33aSHeikki Krogerus  * ERR_PTR(-EPROBE_DEFER) when a connection was found but the switch
69bdecb33aSHeikki Krogerus  * has not been enumerated yet.
70bdecb33aSHeikki Krogerus  */
fwnode_typec_switch_get(struct fwnode_handle * fwnode)71d1c6a769SHeikki Krogerus struct typec_switch *fwnode_typec_switch_get(struct fwnode_handle *fwnode)
72bdecb33aSHeikki Krogerus {
7371793b57SBjorn Andersson 	struct typec_switch_dev *sw_devs[TYPEC_MUX_MAX_DEVS];
74bdecb33aSHeikki Krogerus 	struct typec_switch *sw;
7571793b57SBjorn Andersson 	int count;
7671793b57SBjorn Andersson 	int err;
7771793b57SBjorn Andersson 	int i;
78bdecb33aSHeikki Krogerus 
79713fd49bSBjorn Andersson 	sw = kzalloc(sizeof(*sw), GFP_KERNEL);
80713fd49bSBjorn Andersson 	if (!sw)
81713fd49bSBjorn Andersson 		return ERR_PTR(-ENOMEM);
82713fd49bSBjorn Andersson 
8371793b57SBjorn Andersson 	count = fwnode_connection_find_matches(fwnode, "orientation-switch", NULL,
8471793b57SBjorn Andersson 					       typec_switch_match,
8571793b57SBjorn Andersson 					       (void **)sw_devs,
8671793b57SBjorn Andersson 					       ARRAY_SIZE(sw_devs));
8771793b57SBjorn Andersson 	if (count <= 0) {
88713fd49bSBjorn Andersson 		kfree(sw);
8971793b57SBjorn Andersson 		return NULL;
90713fd49bSBjorn Andersson 	}
91713fd49bSBjorn Andersson 
9271793b57SBjorn Andersson 	for (i = 0; i < count; i++) {
9371793b57SBjorn Andersson 		if (IS_ERR(sw_devs[i])) {
9471793b57SBjorn Andersson 			err = PTR_ERR(sw_devs[i]);
9571793b57SBjorn Andersson 			goto put_sw_devs;
9671793b57SBjorn Andersson 		}
9771793b57SBjorn Andersson 	}
98713fd49bSBjorn Andersson 
9971793b57SBjorn Andersson 	for (i = 0; i < count; i++) {
10071793b57SBjorn Andersson 		WARN_ON(!try_module_get(sw_devs[i]->dev.parent->driver->owner));
10171793b57SBjorn Andersson 		sw->sw_devs[i] = sw_devs[i];
10271793b57SBjorn Andersson 	}
10371793b57SBjorn Andersson 
10471793b57SBjorn Andersson 	sw->num_sw_devs = count;
105bdecb33aSHeikki Krogerus 
106bdecb33aSHeikki Krogerus 	return sw;
10771793b57SBjorn Andersson 
10871793b57SBjorn Andersson put_sw_devs:
10971793b57SBjorn Andersson 	for (i = 0; i < count; i++) {
11071793b57SBjorn Andersson 		if (!IS_ERR(sw_devs[i]))
11171793b57SBjorn Andersson 			put_device(&sw_devs[i]->dev);
11271793b57SBjorn Andersson 	}
11371793b57SBjorn Andersson 
11471793b57SBjorn Andersson 	kfree(sw);
11571793b57SBjorn Andersson 
11671793b57SBjorn Andersson 	return ERR_PTR(err);
117bdecb33aSHeikki Krogerus }
118d1c6a769SHeikki Krogerus EXPORT_SYMBOL_GPL(fwnode_typec_switch_get);
119bdecb33aSHeikki Krogerus 
120bdecb33aSHeikki Krogerus /**
121cbdc0f54SMauro Carvalho Chehab  * typec_switch_put - Release USB Type-C orientation switch
122bdecb33aSHeikki Krogerus  * @sw: USB Type-C orientation switch
123bdecb33aSHeikki Krogerus  *
124bdecb33aSHeikki Krogerus  * Decrement reference count for @sw.
125bdecb33aSHeikki Krogerus  */
typec_switch_put(struct typec_switch * sw)126bdecb33aSHeikki Krogerus void typec_switch_put(struct typec_switch *sw)
127bdecb33aSHeikki Krogerus {
128713fd49bSBjorn Andersson 	struct typec_switch_dev *sw_dev;
12971793b57SBjorn Andersson 	unsigned int i;
130713fd49bSBjorn Andersson 
131713fd49bSBjorn Andersson 	if (IS_ERR_OR_NULL(sw))
132713fd49bSBjorn Andersson 		return;
133713fd49bSBjorn Andersson 
13471793b57SBjorn Andersson 	for (i = 0; i < sw->num_sw_devs; i++) {
13571793b57SBjorn Andersson 		sw_dev = sw->sw_devs[i];
136713fd49bSBjorn Andersson 
137713fd49bSBjorn Andersson 		module_put(sw_dev->dev.parent->driver->owner);
138713fd49bSBjorn Andersson 		put_device(&sw_dev->dev);
13971793b57SBjorn Andersson 	}
140713fd49bSBjorn Andersson 	kfree(sw);
1413e3b8196SHeikki Krogerus }
142bdecb33aSHeikki Krogerus EXPORT_SYMBOL_GPL(typec_switch_put);
143bdecb33aSHeikki Krogerus 
typec_switch_release(struct device * dev)1443370db35SHeikki Krogerus static void typec_switch_release(struct device *dev)
1453370db35SHeikki Krogerus {
146713fd49bSBjorn Andersson 	kfree(to_typec_switch_dev(dev));
1473370db35SHeikki Krogerus }
1483370db35SHeikki Krogerus 
1497bf991eaSHeikki Krogerus const struct device_type typec_switch_dev_type = {
1503370db35SHeikki Krogerus 	.name = "orientation_switch",
1513370db35SHeikki Krogerus 	.release = typec_switch_release,
1523370db35SHeikki Krogerus };
1533370db35SHeikki Krogerus 
154bdecb33aSHeikki Krogerus /**
155bdecb33aSHeikki Krogerus  * typec_switch_register - Register USB Type-C orientation switch
1563370db35SHeikki Krogerus  * @parent: Parent device
1573370db35SHeikki Krogerus  * @desc: Orientation switch description
158bdecb33aSHeikki Krogerus  *
159bdecb33aSHeikki Krogerus  * This function registers a switch that can be used for routing the correct
160bdecb33aSHeikki Krogerus  * data pairs depending on the cable plug orientation from the USB Type-C
161bdecb33aSHeikki Krogerus  * connector to the USB controllers. USB Type-C plugs can be inserted
162bdecb33aSHeikki Krogerus  * right-side-up or upside-down.
163bdecb33aSHeikki Krogerus  */
164713fd49bSBjorn Andersson struct typec_switch_dev *
typec_switch_register(struct device * parent,const struct typec_switch_desc * desc)1653370db35SHeikki Krogerus typec_switch_register(struct device *parent,
1663370db35SHeikki Krogerus 		      const struct typec_switch_desc *desc)
167bdecb33aSHeikki Krogerus {
168713fd49bSBjorn Andersson 	struct typec_switch_dev *sw_dev;
1693370db35SHeikki Krogerus 	int ret;
170bdecb33aSHeikki Krogerus 
1713370db35SHeikki Krogerus 	if (!desc || !desc->set)
1723370db35SHeikki Krogerus 		return ERR_PTR(-EINVAL);
1733370db35SHeikki Krogerus 
174713fd49bSBjorn Andersson 	sw_dev = kzalloc(sizeof(*sw_dev), GFP_KERNEL);
175713fd49bSBjorn Andersson 	if (!sw_dev)
1763370db35SHeikki Krogerus 		return ERR_PTR(-ENOMEM);
1773370db35SHeikki Krogerus 
178713fd49bSBjorn Andersson 	sw_dev->set = desc->set;
1793370db35SHeikki Krogerus 
180713fd49bSBjorn Andersson 	device_initialize(&sw_dev->dev);
181713fd49bSBjorn Andersson 	sw_dev->dev.parent = parent;
182713fd49bSBjorn Andersson 	sw_dev->dev.fwnode = desc->fwnode;
183713fd49bSBjorn Andersson 	sw_dev->dev.class = &typec_mux_class;
184713fd49bSBjorn Andersson 	sw_dev->dev.type = &typec_switch_dev_type;
185713fd49bSBjorn Andersson 	sw_dev->dev.driver_data = desc->drvdata;
186713fd49bSBjorn Andersson 	ret = dev_set_name(&sw_dev->dev, "%s-switch", desc->name ? desc->name : dev_name(parent));
187b9fa0292SBjorn Andersson 	if (ret) {
188713fd49bSBjorn Andersson 		put_device(&sw_dev->dev);
189b9fa0292SBjorn Andersson 		return ERR_PTR(ret);
190b9fa0292SBjorn Andersson 	}
1913370db35SHeikki Krogerus 
192713fd49bSBjorn Andersson 	ret = device_add(&sw_dev->dev);
1933370db35SHeikki Krogerus 	if (ret) {
1943370db35SHeikki Krogerus 		dev_err(parent, "failed to register switch (%d)\n", ret);
195713fd49bSBjorn Andersson 		put_device(&sw_dev->dev);
1963370db35SHeikki Krogerus 		return ERR_PTR(ret);
1973370db35SHeikki Krogerus 	}
1983370db35SHeikki Krogerus 
199713fd49bSBjorn Andersson 	return sw_dev;
200bdecb33aSHeikki Krogerus }
201bdecb33aSHeikki Krogerus EXPORT_SYMBOL_GPL(typec_switch_register);
202bdecb33aSHeikki Krogerus 
typec_switch_set(struct typec_switch * sw,enum typec_orientation orientation)203774a9df6SHeikki Krogerus int typec_switch_set(struct typec_switch *sw,
204774a9df6SHeikki Krogerus 		     enum typec_orientation orientation)
205774a9df6SHeikki Krogerus {
206713fd49bSBjorn Andersson 	struct typec_switch_dev *sw_dev;
20771793b57SBjorn Andersson 	unsigned int i;
20871793b57SBjorn Andersson 	int ret;
209713fd49bSBjorn Andersson 
210774a9df6SHeikki Krogerus 	if (IS_ERR_OR_NULL(sw))
211774a9df6SHeikki Krogerus 		return 0;
212774a9df6SHeikki Krogerus 
21371793b57SBjorn Andersson 	for (i = 0; i < sw->num_sw_devs; i++) {
21471793b57SBjorn Andersson 		sw_dev = sw->sw_devs[i];
215713fd49bSBjorn Andersson 
21671793b57SBjorn Andersson 		ret = sw_dev->set(sw_dev, orientation);
21771793b57SBjorn Andersson 		if (ret)
21871793b57SBjorn Andersson 			return ret;
21971793b57SBjorn Andersson 	}
22071793b57SBjorn Andersson 
22171793b57SBjorn Andersson 	return 0;
222774a9df6SHeikki Krogerus }
223774a9df6SHeikki Krogerus EXPORT_SYMBOL_GPL(typec_switch_set);
224774a9df6SHeikki Krogerus 
225bdecb33aSHeikki Krogerus /**
226bdecb33aSHeikki Krogerus  * typec_switch_unregister - Unregister USB Type-C orientation switch
227713fd49bSBjorn Andersson  * @sw_dev: USB Type-C orientation switch
228bdecb33aSHeikki Krogerus  *
229bdecb33aSHeikki Krogerus  * Unregister switch that was registered with typec_switch_register().
230bdecb33aSHeikki Krogerus  */
typec_switch_unregister(struct typec_switch_dev * sw_dev)231713fd49bSBjorn Andersson void typec_switch_unregister(struct typec_switch_dev *sw_dev)
232bdecb33aSHeikki Krogerus {
233713fd49bSBjorn Andersson 	if (!IS_ERR_OR_NULL(sw_dev))
234713fd49bSBjorn Andersson 		device_unregister(&sw_dev->dev);
235bdecb33aSHeikki Krogerus }
236bdecb33aSHeikki Krogerus EXPORT_SYMBOL_GPL(typec_switch_unregister);
237bdecb33aSHeikki Krogerus 
typec_switch_set_drvdata(struct typec_switch_dev * sw_dev,void * data)238713fd49bSBjorn Andersson void typec_switch_set_drvdata(struct typec_switch_dev *sw_dev, void *data)
2393370db35SHeikki Krogerus {
240713fd49bSBjorn Andersson 	dev_set_drvdata(&sw_dev->dev, data);
2413370db35SHeikki Krogerus }
2423370db35SHeikki Krogerus EXPORT_SYMBOL_GPL(typec_switch_set_drvdata);
2433370db35SHeikki Krogerus 
typec_switch_get_drvdata(struct typec_switch_dev * sw_dev)244713fd49bSBjorn Andersson void *typec_switch_get_drvdata(struct typec_switch_dev *sw_dev)
2453370db35SHeikki Krogerus {
246713fd49bSBjorn Andersson 	return dev_get_drvdata(&sw_dev->dev);
2473370db35SHeikki Krogerus }
2483370db35SHeikki Krogerus EXPORT_SYMBOL_GPL(typec_switch_get_drvdata);
2493370db35SHeikki Krogerus 
250bdecb33aSHeikki Krogerus /* ------------------------------------------------------------------------- */
251bdecb33aSHeikki Krogerus 
252713fd49bSBjorn Andersson struct typec_mux {
25371793b57SBjorn Andersson 	struct typec_mux_dev *mux_devs[TYPEC_MUX_MAX_DEVS];
25471793b57SBjorn Andersson 	unsigned int num_mux_devs;
255713fd49bSBjorn Andersson };
256713fd49bSBjorn Andersson 
mux_fwnode_match(struct device * dev,const void * fwnode)2573370db35SHeikki Krogerus static int mux_fwnode_match(struct device *dev, const void *fwnode)
2583370db35SHeikki Krogerus {
259713fd49bSBjorn Andersson 	if (!is_typec_mux_dev(dev))
2607bf991eaSHeikki Krogerus 		return 0;
2617bf991eaSHeikki Krogerus 
262c9180362SAndy Shevchenko 	return device_match_fwnode(dev, fwnode);
2633370db35SHeikki Krogerus }
2643370db35SHeikki Krogerus 
typec_mux_match(const struct fwnode_handle * fwnode,const char * id,void * data)26523ead33bSAndy Shevchenko static void *typec_mux_match(const struct fwnode_handle *fwnode,
26623ead33bSAndy Shevchenko 			     const char *id, void *data)
267bdecb33aSHeikki Krogerus {
2683370db35SHeikki Krogerus 	struct device *dev;
269bdecb33aSHeikki Krogerus 
270bdecb33aSHeikki Krogerus 	/*
2714aebc4f8SHeikki Krogerus 	 * Device graph (OF graph) does not give any means to identify the
2724aebc4f8SHeikki Krogerus 	 * device type or the device class of the remote port parent that @fwnode
2734aebc4f8SHeikki Krogerus 	 * represents, so in order to identify the type or the class of @fwnode
2744aebc4f8SHeikki Krogerus 	 * an additional device property is needed. With typec muxes the
2754aebc4f8SHeikki Krogerus 	 * property is named "mode-switch" (@id). The value of the device
2764aebc4f8SHeikki Krogerus 	 * property is ignored.
277bdecb33aSHeikki Krogerus 	 */
2784aebc4f8SHeikki Krogerus 	if (id && !fwnode_property_present(fwnode, id))
27996a6d031SHeikki Krogerus 		return NULL;
28096a6d031SHeikki Krogerus 
281f5514c91SHeikki Krogerus 	dev = class_find_device(&typec_mux_class, NULL, fwnode,
2823370db35SHeikki Krogerus 				mux_fwnode_match);
28396a6d031SHeikki Krogerus 
284713fd49bSBjorn Andersson 	return dev ? to_typec_mux_dev(dev) : ERR_PTR(-EPROBE_DEFER);
285bdecb33aSHeikki Krogerus }
286bdecb33aSHeikki Krogerus 
287bdecb33aSHeikki Krogerus /**
288d1c6a769SHeikki Krogerus  * fwnode_typec_mux_get - Find USB Type-C Multiplexer
289d1c6a769SHeikki Krogerus  * @fwnode: The caller device node
290bdecb33aSHeikki Krogerus  *
291bdecb33aSHeikki Krogerus  * Finds a mux linked to the caller. This function is primarily meant for the
292bdecb33aSHeikki Krogerus  * Type-C drivers. Returns a reference to the mux on success, NULL if no
293bdecb33aSHeikki Krogerus  * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection
294bdecb33aSHeikki Krogerus  * was found but the mux has not been enumerated yet.
295bdecb33aSHeikki Krogerus  */
fwnode_typec_mux_get(struct fwnode_handle * fwnode)296*3524fe31SHeikki Krogerus struct typec_mux *fwnode_typec_mux_get(struct fwnode_handle *fwnode)
297bdecb33aSHeikki Krogerus {
29871793b57SBjorn Andersson 	struct typec_mux_dev *mux_devs[TYPEC_MUX_MAX_DEVS];
299bdecb33aSHeikki Krogerus 	struct typec_mux *mux;
30071793b57SBjorn Andersson 	int count;
30171793b57SBjorn Andersson 	int err;
30271793b57SBjorn Andersson 	int i;
303bdecb33aSHeikki Krogerus 
304713fd49bSBjorn Andersson 	mux = kzalloc(sizeof(*mux), GFP_KERNEL);
305713fd49bSBjorn Andersson 	if (!mux)
306713fd49bSBjorn Andersson 		return ERR_PTR(-ENOMEM);
307713fd49bSBjorn Andersson 
30871793b57SBjorn Andersson 	count = fwnode_connection_find_matches(fwnode, "mode-switch",
309*3524fe31SHeikki Krogerus 					       NULL, typec_mux_match,
31071793b57SBjorn Andersson 					       (void **)mux_devs,
31171793b57SBjorn Andersson 					       ARRAY_SIZE(mux_devs));
31271793b57SBjorn Andersson 	if (count <= 0) {
313713fd49bSBjorn Andersson 		kfree(mux);
31471793b57SBjorn Andersson 		return NULL;
315713fd49bSBjorn Andersson 	}
316713fd49bSBjorn Andersson 
31771793b57SBjorn Andersson 	for (i = 0; i < count; i++) {
31871793b57SBjorn Andersson 		if (IS_ERR(mux_devs[i])) {
31971793b57SBjorn Andersson 			err = PTR_ERR(mux_devs[i]);
32071793b57SBjorn Andersson 			goto put_mux_devs;
32171793b57SBjorn Andersson 		}
32271793b57SBjorn Andersson 	}
323713fd49bSBjorn Andersson 
32471793b57SBjorn Andersson 	for (i = 0; i < count; i++) {
32571793b57SBjorn Andersson 		WARN_ON(!try_module_get(mux_devs[i]->dev.parent->driver->owner));
32671793b57SBjorn Andersson 		mux->mux_devs[i] = mux_devs[i];
32771793b57SBjorn Andersson 	}
32871793b57SBjorn Andersson 
32971793b57SBjorn Andersson 	mux->num_mux_devs = count;
330bdecb33aSHeikki Krogerus 
331bdecb33aSHeikki Krogerus 	return mux;
33271793b57SBjorn Andersson 
33371793b57SBjorn Andersson put_mux_devs:
33471793b57SBjorn Andersson 	for (i = 0; i < count; i++) {
33571793b57SBjorn Andersson 		if (!IS_ERR(mux_devs[i]))
33671793b57SBjorn Andersson 			put_device(&mux_devs[i]->dev);
33771793b57SBjorn Andersson 	}
33871793b57SBjorn Andersson 
33971793b57SBjorn Andersson 	kfree(mux);
34071793b57SBjorn Andersson 
34171793b57SBjorn Andersson 	return ERR_PTR(err);
342bdecb33aSHeikki Krogerus }
343d1c6a769SHeikki Krogerus EXPORT_SYMBOL_GPL(fwnode_typec_mux_get);
344bdecb33aSHeikki Krogerus 
345bdecb33aSHeikki Krogerus /**
346bdecb33aSHeikki Krogerus  * typec_mux_put - Release handle to a Multiplexer
347bdecb33aSHeikki Krogerus  * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
348bdecb33aSHeikki Krogerus  *
349bdecb33aSHeikki Krogerus  * Decrements reference count for @mux.
350bdecb33aSHeikki Krogerus  */
typec_mux_put(struct typec_mux * mux)351bdecb33aSHeikki Krogerus void typec_mux_put(struct typec_mux *mux)
352bdecb33aSHeikki Krogerus {
353713fd49bSBjorn Andersson 	struct typec_mux_dev *mux_dev;
35471793b57SBjorn Andersson 	unsigned int i;
355713fd49bSBjorn Andersson 
356713fd49bSBjorn Andersson 	if (IS_ERR_OR_NULL(mux))
357713fd49bSBjorn Andersson 		return;
358713fd49bSBjorn Andersson 
35971793b57SBjorn Andersson 	for (i = 0; i < mux->num_mux_devs; i++) {
36071793b57SBjorn Andersson 		mux_dev = mux->mux_devs[i];
361713fd49bSBjorn Andersson 		module_put(mux_dev->dev.parent->driver->owner);
362713fd49bSBjorn Andersson 		put_device(&mux_dev->dev);
36371793b57SBjorn Andersson 	}
364713fd49bSBjorn Andersson 	kfree(mux);
3653e3b8196SHeikki Krogerus }
366bdecb33aSHeikki Krogerus EXPORT_SYMBOL_GPL(typec_mux_put);
367bdecb33aSHeikki Krogerus 
typec_mux_set(struct typec_mux * mux,struct typec_mux_state * state)368774a9df6SHeikki Krogerus int typec_mux_set(struct typec_mux *mux, struct typec_mux_state *state)
369774a9df6SHeikki Krogerus {
370713fd49bSBjorn Andersson 	struct typec_mux_dev *mux_dev;
37171793b57SBjorn Andersson 	unsigned int i;
37271793b57SBjorn Andersson 	int ret;
373713fd49bSBjorn Andersson 
374774a9df6SHeikki Krogerus 	if (IS_ERR_OR_NULL(mux))
375774a9df6SHeikki Krogerus 		return 0;
376774a9df6SHeikki Krogerus 
37771793b57SBjorn Andersson 	for (i = 0; i < mux->num_mux_devs; i++) {
37871793b57SBjorn Andersson 		mux_dev = mux->mux_devs[i];
379713fd49bSBjorn Andersson 
38071793b57SBjorn Andersson 		ret = mux_dev->set(mux_dev, state);
38171793b57SBjorn Andersson 		if (ret)
38271793b57SBjorn Andersson 			return ret;
38371793b57SBjorn Andersson 	}
38471793b57SBjorn Andersson 
38571793b57SBjorn Andersson 	return 0;
386774a9df6SHeikki Krogerus }
387774a9df6SHeikki Krogerus EXPORT_SYMBOL_GPL(typec_mux_set);
388774a9df6SHeikki Krogerus 
typec_mux_release(struct device * dev)3893370db35SHeikki Krogerus static void typec_mux_release(struct device *dev)
3903370db35SHeikki Krogerus {
391713fd49bSBjorn Andersson 	kfree(to_typec_mux_dev(dev));
3923370db35SHeikki Krogerus }
3933370db35SHeikki Krogerus 
3947bf991eaSHeikki Krogerus const struct device_type typec_mux_dev_type = {
3953370db35SHeikki Krogerus 	.name = "mode_switch",
3963370db35SHeikki Krogerus 	.release = typec_mux_release,
3973370db35SHeikki Krogerus };
3983370db35SHeikki Krogerus 
399bdecb33aSHeikki Krogerus /**
400bdecb33aSHeikki Krogerus  * typec_mux_register - Register Multiplexer routing USB Type-C pins
4013370db35SHeikki Krogerus  * @parent: Parent device
4023370db35SHeikki Krogerus  * @desc: Multiplexer description
403bdecb33aSHeikki Krogerus  *
404bdecb33aSHeikki Krogerus  * USB Type-C connectors can be used for alternate modes of operation besides
405bdecb33aSHeikki Krogerus  * USB when Accessory/Alternate Modes are supported. With some of those modes,
406bdecb33aSHeikki Krogerus  * the pins on the connector need to be reconfigured. This function registers
407bdecb33aSHeikki Krogerus  * multiplexer switches routing the pins on the connector.
408bdecb33aSHeikki Krogerus  */
409713fd49bSBjorn Andersson struct typec_mux_dev *
typec_mux_register(struct device * parent,const struct typec_mux_desc * desc)4103370db35SHeikki Krogerus typec_mux_register(struct device *parent, const struct typec_mux_desc *desc)
411bdecb33aSHeikki Krogerus {
412713fd49bSBjorn Andersson 	struct typec_mux_dev *mux_dev;
4133370db35SHeikki Krogerus 	int ret;
414bdecb33aSHeikki Krogerus 
4153370db35SHeikki Krogerus 	if (!desc || !desc->set)
4163370db35SHeikki Krogerus 		return ERR_PTR(-EINVAL);
4173370db35SHeikki Krogerus 
418713fd49bSBjorn Andersson 	mux_dev = kzalloc(sizeof(*mux_dev), GFP_KERNEL);
419713fd49bSBjorn Andersson 	if (!mux_dev)
4203370db35SHeikki Krogerus 		return ERR_PTR(-ENOMEM);
4213370db35SHeikki Krogerus 
422713fd49bSBjorn Andersson 	mux_dev->set = desc->set;
4233370db35SHeikki Krogerus 
424713fd49bSBjorn Andersson 	device_initialize(&mux_dev->dev);
425713fd49bSBjorn Andersson 	mux_dev->dev.parent = parent;
426713fd49bSBjorn Andersson 	mux_dev->dev.fwnode = desc->fwnode;
427713fd49bSBjorn Andersson 	mux_dev->dev.class = &typec_mux_class;
428713fd49bSBjorn Andersson 	mux_dev->dev.type = &typec_mux_dev_type;
429713fd49bSBjorn Andersson 	mux_dev->dev.driver_data = desc->drvdata;
430713fd49bSBjorn Andersson 	ret = dev_set_name(&mux_dev->dev, "%s-mux", desc->name ? desc->name : dev_name(parent));
431b9fa0292SBjorn Andersson 	if (ret) {
432713fd49bSBjorn Andersson 		put_device(&mux_dev->dev);
433b9fa0292SBjorn Andersson 		return ERR_PTR(ret);
434b9fa0292SBjorn Andersson 	}
4353370db35SHeikki Krogerus 
436713fd49bSBjorn Andersson 	ret = device_add(&mux_dev->dev);
4373370db35SHeikki Krogerus 	if (ret) {
4383370db35SHeikki Krogerus 		dev_err(parent, "failed to register mux (%d)\n", ret);
439713fd49bSBjorn Andersson 		put_device(&mux_dev->dev);
4403370db35SHeikki Krogerus 		return ERR_PTR(ret);
4413370db35SHeikki Krogerus 	}
4423370db35SHeikki Krogerus 
443713fd49bSBjorn Andersson 	return mux_dev;
444bdecb33aSHeikki Krogerus }
445bdecb33aSHeikki Krogerus EXPORT_SYMBOL_GPL(typec_mux_register);
446bdecb33aSHeikki Krogerus 
447bdecb33aSHeikki Krogerus /**
448bdecb33aSHeikki Krogerus  * typec_mux_unregister - Unregister Multiplexer Switch
449713fd49bSBjorn Andersson  * @mux_dev: USB Type-C Connector Multiplexer/DeMultiplexer
450bdecb33aSHeikki Krogerus  *
451bdecb33aSHeikki Krogerus  * Unregister mux that was registered with typec_mux_register().
452bdecb33aSHeikki Krogerus  */
typec_mux_unregister(struct typec_mux_dev * mux_dev)453713fd49bSBjorn Andersson void typec_mux_unregister(struct typec_mux_dev *mux_dev)
454bdecb33aSHeikki Krogerus {
455713fd49bSBjorn Andersson 	if (!IS_ERR_OR_NULL(mux_dev))
456713fd49bSBjorn Andersson 		device_unregister(&mux_dev->dev);
457bdecb33aSHeikki Krogerus }
458bdecb33aSHeikki Krogerus EXPORT_SYMBOL_GPL(typec_mux_unregister);
4593370db35SHeikki Krogerus 
typec_mux_set_drvdata(struct typec_mux_dev * mux_dev,void * data)460713fd49bSBjorn Andersson void typec_mux_set_drvdata(struct typec_mux_dev *mux_dev, void *data)
4613370db35SHeikki Krogerus {
462713fd49bSBjorn Andersson 	dev_set_drvdata(&mux_dev->dev, data);
4633370db35SHeikki Krogerus }
4643370db35SHeikki Krogerus EXPORT_SYMBOL_GPL(typec_mux_set_drvdata);
4653370db35SHeikki Krogerus 
typec_mux_get_drvdata(struct typec_mux_dev * mux_dev)466713fd49bSBjorn Andersson void *typec_mux_get_drvdata(struct typec_mux_dev *mux_dev)
4673370db35SHeikki Krogerus {
468713fd49bSBjorn Andersson 	return dev_get_drvdata(&mux_dev->dev);
4693370db35SHeikki Krogerus }
4703370db35SHeikki Krogerus EXPORT_SYMBOL_GPL(typec_mux_get_drvdata);
4713370db35SHeikki Krogerus 
4723370db35SHeikki Krogerus struct class typec_mux_class = {
4733370db35SHeikki Krogerus 	.name = "typec_mux",
4743370db35SHeikki Krogerus };
475