xref: /openbmc/linux/drivers/usb/typec/mux.c (revision 96a6d031)
1bdecb33aSHeikki Krogerus // SPDX-License-Identifier: GPL-2.0
2bdecb33aSHeikki Krogerus /**
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 #include <linux/usb/typec_mux.h>
17bdecb33aSHeikki Krogerus 
18bdecb33aSHeikki Krogerus static DEFINE_MUTEX(switch_lock);
19bdecb33aSHeikki Krogerus static DEFINE_MUTEX(mux_lock);
20bdecb33aSHeikki Krogerus static LIST_HEAD(switch_list);
21bdecb33aSHeikki Krogerus static LIST_HEAD(mux_list);
22bdecb33aSHeikki Krogerus 
23bdecb33aSHeikki Krogerus static void *typec_switch_match(struct device_connection *con, int ep,
24bdecb33aSHeikki Krogerus 				void *data)
25bdecb33aSHeikki Krogerus {
26bdecb33aSHeikki Krogerus 	struct typec_switch *sw;
27bdecb33aSHeikki Krogerus 
2896a6d031SHeikki Krogerus 	if (!con->fwnode) {
29bdecb33aSHeikki Krogerus 		list_for_each_entry(sw, &switch_list, entry)
30bdecb33aSHeikki Krogerus 			if (!strcmp(con->endpoint[ep], dev_name(sw->dev)))
31bdecb33aSHeikki Krogerus 				return sw;
3296a6d031SHeikki Krogerus 		return ERR_PTR(-EPROBE_DEFER);
3396a6d031SHeikki Krogerus 	}
34bdecb33aSHeikki Krogerus 
35bdecb33aSHeikki Krogerus 	/*
3696a6d031SHeikki Krogerus 	 * With OF graph the mux node must have a boolean device property named
3796a6d031SHeikki Krogerus 	 * "orientation-switch".
38bdecb33aSHeikki Krogerus 	 */
3996a6d031SHeikki Krogerus 	if (con->id && !fwnode_property_present(con->fwnode, con->id))
4096a6d031SHeikki Krogerus 		return NULL;
4196a6d031SHeikki Krogerus 
4296a6d031SHeikki Krogerus 	list_for_each_entry(sw, &switch_list, entry)
4396a6d031SHeikki Krogerus 		if (dev_fwnode(sw->dev) == con->fwnode)
4496a6d031SHeikki Krogerus 			return sw;
4596a6d031SHeikki Krogerus 
4696a6d031SHeikki Krogerus 	return con->id ? ERR_PTR(-EPROBE_DEFER) : NULL;
47bdecb33aSHeikki Krogerus }
48bdecb33aSHeikki Krogerus 
49bdecb33aSHeikki Krogerus /**
50bdecb33aSHeikki Krogerus  * typec_switch_get - Find USB Type-C orientation switch
51bdecb33aSHeikki Krogerus  * @dev: The caller device
52bdecb33aSHeikki Krogerus  *
53bdecb33aSHeikki Krogerus  * Finds a switch linked with @dev. Returns a reference to the switch on
54bdecb33aSHeikki Krogerus  * success, NULL if no matching connection was found, or
55bdecb33aSHeikki Krogerus  * ERR_PTR(-EPROBE_DEFER) when a connection was found but the switch
56bdecb33aSHeikki Krogerus  * has not been enumerated yet.
57bdecb33aSHeikki Krogerus  */
58bdecb33aSHeikki Krogerus struct typec_switch *typec_switch_get(struct device *dev)
59bdecb33aSHeikki Krogerus {
60bdecb33aSHeikki Krogerus 	struct typec_switch *sw;
61bdecb33aSHeikki Krogerus 
62bdecb33aSHeikki Krogerus 	mutex_lock(&switch_lock);
63540bfab7SHeikki Krogerus 	sw = device_connection_find_match(dev, "orientation-switch", NULL,
64bdecb33aSHeikki Krogerus 					  typec_switch_match);
653e3b8196SHeikki Krogerus 	if (!IS_ERR_OR_NULL(sw)) {
663e3b8196SHeikki Krogerus 		WARN_ON(!try_module_get(sw->dev->driver->owner));
67bdecb33aSHeikki Krogerus 		get_device(sw->dev);
683e3b8196SHeikki Krogerus 	}
69bdecb33aSHeikki Krogerus 	mutex_unlock(&switch_lock);
70bdecb33aSHeikki Krogerus 
71bdecb33aSHeikki Krogerus 	return sw;
72bdecb33aSHeikki Krogerus }
73bdecb33aSHeikki Krogerus EXPORT_SYMBOL_GPL(typec_switch_get);
74bdecb33aSHeikki Krogerus 
75bdecb33aSHeikki Krogerus /**
76bdecb33aSHeikki Krogerus  * typec_put_switch - Release USB Type-C orientation switch
77bdecb33aSHeikki Krogerus  * @sw: USB Type-C orientation switch
78bdecb33aSHeikki Krogerus  *
79bdecb33aSHeikki Krogerus  * Decrement reference count for @sw.
80bdecb33aSHeikki Krogerus  */
81bdecb33aSHeikki Krogerus void typec_switch_put(struct typec_switch *sw)
82bdecb33aSHeikki Krogerus {
833e3b8196SHeikki Krogerus 	if (!IS_ERR_OR_NULL(sw)) {
843e3b8196SHeikki Krogerus 		module_put(sw->dev->driver->owner);
85bdecb33aSHeikki Krogerus 		put_device(sw->dev);
86bdecb33aSHeikki Krogerus 	}
873e3b8196SHeikki Krogerus }
88bdecb33aSHeikki Krogerus EXPORT_SYMBOL_GPL(typec_switch_put);
89bdecb33aSHeikki Krogerus 
90bdecb33aSHeikki Krogerus /**
91bdecb33aSHeikki Krogerus  * typec_switch_register - Register USB Type-C orientation switch
92bdecb33aSHeikki Krogerus  * @sw: USB Type-C orientation switch
93bdecb33aSHeikki Krogerus  *
94bdecb33aSHeikki Krogerus  * This function registers a switch that can be used for routing the correct
95bdecb33aSHeikki Krogerus  * data pairs depending on the cable plug orientation from the USB Type-C
96bdecb33aSHeikki Krogerus  * connector to the USB controllers. USB Type-C plugs can be inserted
97bdecb33aSHeikki Krogerus  * right-side-up or upside-down.
98bdecb33aSHeikki Krogerus  */
99bdecb33aSHeikki Krogerus int typec_switch_register(struct typec_switch *sw)
100bdecb33aSHeikki Krogerus {
101bdecb33aSHeikki Krogerus 	mutex_lock(&switch_lock);
102bdecb33aSHeikki Krogerus 	list_add_tail(&sw->entry, &switch_list);
103bdecb33aSHeikki Krogerus 	mutex_unlock(&switch_lock);
104bdecb33aSHeikki Krogerus 
105bdecb33aSHeikki Krogerus 	return 0;
106bdecb33aSHeikki Krogerus }
107bdecb33aSHeikki Krogerus EXPORT_SYMBOL_GPL(typec_switch_register);
108bdecb33aSHeikki Krogerus 
109bdecb33aSHeikki Krogerus /**
110bdecb33aSHeikki Krogerus  * typec_switch_unregister - Unregister USB Type-C orientation switch
111bdecb33aSHeikki Krogerus  * @sw: USB Type-C orientation switch
112bdecb33aSHeikki Krogerus  *
113bdecb33aSHeikki Krogerus  * Unregister switch that was registered with typec_switch_register().
114bdecb33aSHeikki Krogerus  */
115bdecb33aSHeikki Krogerus void typec_switch_unregister(struct typec_switch *sw)
116bdecb33aSHeikki Krogerus {
117bdecb33aSHeikki Krogerus 	mutex_lock(&switch_lock);
118bdecb33aSHeikki Krogerus 	list_del(&sw->entry);
119bdecb33aSHeikki Krogerus 	mutex_unlock(&switch_lock);
120bdecb33aSHeikki Krogerus }
121bdecb33aSHeikki Krogerus EXPORT_SYMBOL_GPL(typec_switch_unregister);
122bdecb33aSHeikki Krogerus 
123bdecb33aSHeikki Krogerus /* ------------------------------------------------------------------------- */
124bdecb33aSHeikki Krogerus 
125bdecb33aSHeikki Krogerus static void *typec_mux_match(struct device_connection *con, int ep, void *data)
126bdecb33aSHeikki Krogerus {
12796a6d031SHeikki Krogerus 	const struct typec_altmode_desc *desc = data;
128bdecb33aSHeikki Krogerus 	struct typec_mux *mux;
12996a6d031SHeikki Krogerus 	size_t nval;
13096a6d031SHeikki Krogerus 	bool match;
13196a6d031SHeikki Krogerus 	u16 *val;
13296a6d031SHeikki Krogerus 	int i;
133bdecb33aSHeikki Krogerus 
13496a6d031SHeikki Krogerus 	if (!con->fwnode) {
135bdecb33aSHeikki Krogerus 		list_for_each_entry(mux, &mux_list, entry)
136bdecb33aSHeikki Krogerus 			if (!strcmp(con->endpoint[ep], dev_name(mux->dev)))
137bdecb33aSHeikki Krogerus 				return mux;
13896a6d031SHeikki Krogerus 		return ERR_PTR(-EPROBE_DEFER);
13996a6d031SHeikki Krogerus 	}
140bdecb33aSHeikki Krogerus 
141bdecb33aSHeikki Krogerus 	/*
14296a6d031SHeikki Krogerus 	 * Check has the identifier already been "consumed". If it
14396a6d031SHeikki Krogerus 	 * has, no need to do any extra connection identification.
144bdecb33aSHeikki Krogerus 	 */
14596a6d031SHeikki Krogerus 	match = !con->id;
14696a6d031SHeikki Krogerus 	if (match)
14796a6d031SHeikki Krogerus 		goto find_mux;
14896a6d031SHeikki Krogerus 
14996a6d031SHeikki Krogerus 	/* Accessory Mode muxes */
15096a6d031SHeikki Krogerus 	if (!desc) {
15196a6d031SHeikki Krogerus 		match = fwnode_property_present(con->fwnode, "accessory");
15296a6d031SHeikki Krogerus 		if (match)
15396a6d031SHeikki Krogerus 			goto find_mux;
15496a6d031SHeikki Krogerus 		return NULL;
15596a6d031SHeikki Krogerus 	}
15696a6d031SHeikki Krogerus 
15796a6d031SHeikki Krogerus 	/* Alternate Mode muxes */
15896a6d031SHeikki Krogerus 	nval = fwnode_property_read_u16_array(con->fwnode, "svid", NULL, 0);
15996a6d031SHeikki Krogerus 	if (nval <= 0)
16096a6d031SHeikki Krogerus 		return NULL;
16196a6d031SHeikki Krogerus 
16296a6d031SHeikki Krogerus 	val = kcalloc(nval, sizeof(*val), GFP_KERNEL);
16396a6d031SHeikki Krogerus 	if (!val)
16496a6d031SHeikki Krogerus 		return ERR_PTR(-ENOMEM);
16596a6d031SHeikki Krogerus 
16696a6d031SHeikki Krogerus 	nval = fwnode_property_read_u16_array(con->fwnode, "svid", val, nval);
16796a6d031SHeikki Krogerus 	if (nval < 0) {
16896a6d031SHeikki Krogerus 		kfree(val);
16996a6d031SHeikki Krogerus 		return ERR_PTR(nval);
17096a6d031SHeikki Krogerus 	}
17196a6d031SHeikki Krogerus 
17296a6d031SHeikki Krogerus 	for (i = 0; i < nval; i++) {
17396a6d031SHeikki Krogerus 		match = val[i] == desc->svid;
17496a6d031SHeikki Krogerus 		if (match) {
17596a6d031SHeikki Krogerus 			kfree(val);
17696a6d031SHeikki Krogerus 			goto find_mux;
17796a6d031SHeikki Krogerus 		}
17896a6d031SHeikki Krogerus 	}
17996a6d031SHeikki Krogerus 	kfree(val);
18096a6d031SHeikki Krogerus 	return NULL;
18196a6d031SHeikki Krogerus 
18296a6d031SHeikki Krogerus find_mux:
18396a6d031SHeikki Krogerus 	list_for_each_entry(mux, &mux_list, entry)
18496a6d031SHeikki Krogerus 		if (dev_fwnode(mux->dev) == con->fwnode)
18596a6d031SHeikki Krogerus 			return mux;
18696a6d031SHeikki Krogerus 
18796a6d031SHeikki Krogerus 	return match ? ERR_PTR(-EPROBE_DEFER) : NULL;
188bdecb33aSHeikki Krogerus }
189bdecb33aSHeikki Krogerus 
190bdecb33aSHeikki Krogerus /**
191bdecb33aSHeikki Krogerus  * typec_mux_get - Find USB Type-C Multiplexer
192bdecb33aSHeikki Krogerus  * @dev: The caller device
193540bfab7SHeikki Krogerus  * @desc: Alt Mode description
194bdecb33aSHeikki Krogerus  *
195bdecb33aSHeikki Krogerus  * Finds a mux linked to the caller. This function is primarily meant for the
196bdecb33aSHeikki Krogerus  * Type-C drivers. Returns a reference to the mux on success, NULL if no
197bdecb33aSHeikki Krogerus  * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection
198bdecb33aSHeikki Krogerus  * was found but the mux has not been enumerated yet.
199bdecb33aSHeikki Krogerus  */
200540bfab7SHeikki Krogerus struct typec_mux *typec_mux_get(struct device *dev,
201540bfab7SHeikki Krogerus 				const struct typec_altmode_desc *desc)
202bdecb33aSHeikki Krogerus {
203bdecb33aSHeikki Krogerus 	struct typec_mux *mux;
204bdecb33aSHeikki Krogerus 
205bdecb33aSHeikki Krogerus 	mutex_lock(&mux_lock);
206540bfab7SHeikki Krogerus 	mux = device_connection_find_match(dev, "mode-switch", (void *)desc,
207540bfab7SHeikki Krogerus 					   typec_mux_match);
2083e3b8196SHeikki Krogerus 	if (!IS_ERR_OR_NULL(mux)) {
2093e3b8196SHeikki Krogerus 		WARN_ON(!try_module_get(mux->dev->driver->owner));
210bdecb33aSHeikki Krogerus 		get_device(mux->dev);
2113e3b8196SHeikki Krogerus 	}
212bdecb33aSHeikki Krogerus 	mutex_unlock(&mux_lock);
213bdecb33aSHeikki Krogerus 
214bdecb33aSHeikki Krogerus 	return mux;
215bdecb33aSHeikki Krogerus }
216bdecb33aSHeikki Krogerus EXPORT_SYMBOL_GPL(typec_mux_get);
217bdecb33aSHeikki Krogerus 
218bdecb33aSHeikki Krogerus /**
219bdecb33aSHeikki Krogerus  * typec_mux_put - Release handle to a Multiplexer
220bdecb33aSHeikki Krogerus  * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
221bdecb33aSHeikki Krogerus  *
222bdecb33aSHeikki Krogerus  * Decrements reference count for @mux.
223bdecb33aSHeikki Krogerus  */
224bdecb33aSHeikki Krogerus void typec_mux_put(struct typec_mux *mux)
225bdecb33aSHeikki Krogerus {
2263e3b8196SHeikki Krogerus 	if (!IS_ERR_OR_NULL(mux)) {
2273e3b8196SHeikki Krogerus 		module_put(mux->dev->driver->owner);
228bdecb33aSHeikki Krogerus 		put_device(mux->dev);
229bdecb33aSHeikki Krogerus 	}
2303e3b8196SHeikki Krogerus }
231bdecb33aSHeikki Krogerus EXPORT_SYMBOL_GPL(typec_mux_put);
232bdecb33aSHeikki Krogerus 
233bdecb33aSHeikki Krogerus /**
234bdecb33aSHeikki Krogerus  * typec_mux_register - Register Multiplexer routing USB Type-C pins
235bdecb33aSHeikki Krogerus  * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
236bdecb33aSHeikki Krogerus  *
237bdecb33aSHeikki Krogerus  * USB Type-C connectors can be used for alternate modes of operation besides
238bdecb33aSHeikki Krogerus  * USB when Accessory/Alternate Modes are supported. With some of those modes,
239bdecb33aSHeikki Krogerus  * the pins on the connector need to be reconfigured. This function registers
240bdecb33aSHeikki Krogerus  * multiplexer switches routing the pins on the connector.
241bdecb33aSHeikki Krogerus  */
242bdecb33aSHeikki Krogerus int typec_mux_register(struct typec_mux *mux)
243bdecb33aSHeikki Krogerus {
244bdecb33aSHeikki Krogerus 	mutex_lock(&mux_lock);
245bdecb33aSHeikki Krogerus 	list_add_tail(&mux->entry, &mux_list);
246bdecb33aSHeikki Krogerus 	mutex_unlock(&mux_lock);
247bdecb33aSHeikki Krogerus 
248bdecb33aSHeikki Krogerus 	return 0;
249bdecb33aSHeikki Krogerus }
250bdecb33aSHeikki Krogerus EXPORT_SYMBOL_GPL(typec_mux_register);
251bdecb33aSHeikki Krogerus 
252bdecb33aSHeikki Krogerus /**
253bdecb33aSHeikki Krogerus  * typec_mux_unregister - Unregister Multiplexer Switch
25474789aedSHeikki Krogerus  * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
255bdecb33aSHeikki Krogerus  *
256bdecb33aSHeikki Krogerus  * Unregister mux that was registered with typec_mux_register().
257bdecb33aSHeikki Krogerus  */
258bdecb33aSHeikki Krogerus void typec_mux_unregister(struct typec_mux *mux)
259bdecb33aSHeikki Krogerus {
260bdecb33aSHeikki Krogerus 	mutex_lock(&mux_lock);
261bdecb33aSHeikki Krogerus 	list_del(&mux->entry);
262bdecb33aSHeikki Krogerus 	mutex_unlock(&mux_lock);
263bdecb33aSHeikki Krogerus }
264bdecb33aSHeikki Krogerus EXPORT_SYMBOL_GPL(typec_mux_unregister);
265