xref: /openbmc/linux/drivers/usb/typec/mux.c (revision 540bfab7)
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/usb/typec_mux.h>
15 
16 static DEFINE_MUTEX(switch_lock);
17 static DEFINE_MUTEX(mux_lock);
18 static LIST_HEAD(switch_list);
19 static LIST_HEAD(mux_list);
20 
21 static void *typec_switch_match(struct device_connection *con, int ep,
22 				void *data)
23 {
24 	struct typec_switch *sw;
25 
26 	list_for_each_entry(sw, &switch_list, entry)
27 		if (!strcmp(con->endpoint[ep], dev_name(sw->dev)))
28 			return sw;
29 
30 	/*
31 	 * We only get called if a connection was found, tell the caller to
32 	 * wait for the switch to show up.
33 	 */
34 	return ERR_PTR(-EPROBE_DEFER);
35 }
36 
37 /**
38  * typec_switch_get - Find USB Type-C orientation switch
39  * @dev: The caller device
40  *
41  * Finds a switch linked with @dev. Returns a reference to the switch on
42  * success, NULL if no matching connection was found, or
43  * ERR_PTR(-EPROBE_DEFER) when a connection was found but the switch
44  * has not been enumerated yet.
45  */
46 struct typec_switch *typec_switch_get(struct device *dev)
47 {
48 	struct typec_switch *sw;
49 
50 	mutex_lock(&switch_lock);
51 	sw = device_connection_find_match(dev, "orientation-switch", NULL,
52 					  typec_switch_match);
53 	if (!IS_ERR_OR_NULL(sw)) {
54 		WARN_ON(!try_module_get(sw->dev->driver->owner));
55 		get_device(sw->dev);
56 	}
57 	mutex_unlock(&switch_lock);
58 
59 	return sw;
60 }
61 EXPORT_SYMBOL_GPL(typec_switch_get);
62 
63 /**
64  * typec_put_switch - Release USB Type-C orientation switch
65  * @sw: USB Type-C orientation switch
66  *
67  * Decrement reference count for @sw.
68  */
69 void typec_switch_put(struct typec_switch *sw)
70 {
71 	if (!IS_ERR_OR_NULL(sw)) {
72 		module_put(sw->dev->driver->owner);
73 		put_device(sw->dev);
74 	}
75 }
76 EXPORT_SYMBOL_GPL(typec_switch_put);
77 
78 /**
79  * typec_switch_register - Register USB Type-C orientation switch
80  * @sw: USB Type-C orientation switch
81  *
82  * This function registers a switch that can be used for routing the correct
83  * data pairs depending on the cable plug orientation from the USB Type-C
84  * connector to the USB controllers. USB Type-C plugs can be inserted
85  * right-side-up or upside-down.
86  */
87 int typec_switch_register(struct typec_switch *sw)
88 {
89 	mutex_lock(&switch_lock);
90 	list_add_tail(&sw->entry, &switch_list);
91 	mutex_unlock(&switch_lock);
92 
93 	return 0;
94 }
95 EXPORT_SYMBOL_GPL(typec_switch_register);
96 
97 /**
98  * typec_switch_unregister - Unregister USB Type-C orientation switch
99  * @sw: USB Type-C orientation switch
100  *
101  * Unregister switch that was registered with typec_switch_register().
102  */
103 void typec_switch_unregister(struct typec_switch *sw)
104 {
105 	mutex_lock(&switch_lock);
106 	list_del(&sw->entry);
107 	mutex_unlock(&switch_lock);
108 }
109 EXPORT_SYMBOL_GPL(typec_switch_unregister);
110 
111 /* ------------------------------------------------------------------------- */
112 
113 static void *typec_mux_match(struct device_connection *con, int ep, void *data)
114 {
115 	struct typec_mux *mux;
116 
117 	list_for_each_entry(mux, &mux_list, entry)
118 		if (!strcmp(con->endpoint[ep], dev_name(mux->dev)))
119 			return mux;
120 
121 	/*
122 	 * We only get called if a connection was found, tell the caller to
123 	 * wait for the switch to show up.
124 	 */
125 	return ERR_PTR(-EPROBE_DEFER);
126 }
127 
128 /**
129  * typec_mux_get - Find USB Type-C Multiplexer
130  * @dev: The caller device
131  * @desc: Alt Mode description
132  *
133  * Finds a mux linked to the caller. This function is primarily meant for the
134  * Type-C drivers. Returns a reference to the mux on success, NULL if no
135  * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection
136  * was found but the mux has not been enumerated yet.
137  */
138 struct typec_mux *typec_mux_get(struct device *dev,
139 				const struct typec_altmode_desc *desc)
140 {
141 	struct typec_mux *mux;
142 
143 	mutex_lock(&mux_lock);
144 	mux = device_connection_find_match(dev, "mode-switch", (void *)desc,
145 					   typec_mux_match);
146 	if (!IS_ERR_OR_NULL(mux)) {
147 		WARN_ON(!try_module_get(mux->dev->driver->owner));
148 		get_device(mux->dev);
149 	}
150 	mutex_unlock(&mux_lock);
151 
152 	return mux;
153 }
154 EXPORT_SYMBOL_GPL(typec_mux_get);
155 
156 /**
157  * typec_mux_put - Release handle to a Multiplexer
158  * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
159  *
160  * Decrements reference count for @mux.
161  */
162 void typec_mux_put(struct typec_mux *mux)
163 {
164 	if (!IS_ERR_OR_NULL(mux)) {
165 		module_put(mux->dev->driver->owner);
166 		put_device(mux->dev);
167 	}
168 }
169 EXPORT_SYMBOL_GPL(typec_mux_put);
170 
171 /**
172  * typec_mux_register - Register Multiplexer routing USB Type-C pins
173  * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
174  *
175  * USB Type-C connectors can be used for alternate modes of operation besides
176  * USB when Accessory/Alternate Modes are supported. With some of those modes,
177  * the pins on the connector need to be reconfigured. This function registers
178  * multiplexer switches routing the pins on the connector.
179  */
180 int typec_mux_register(struct typec_mux *mux)
181 {
182 	mutex_lock(&mux_lock);
183 	list_add_tail(&mux->entry, &mux_list);
184 	mutex_unlock(&mux_lock);
185 
186 	return 0;
187 }
188 EXPORT_SYMBOL_GPL(typec_mux_register);
189 
190 /**
191  * typec_mux_unregister - Unregister Multiplexer Switch
192  * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
193  *
194  * Unregister mux that was registered with typec_mux_register().
195  */
196 void typec_mux_unregister(struct typec_mux *mux)
197 {
198 	mutex_lock(&mux_lock);
199 	list_del(&mux->entry);
200 	mutex_unlock(&mux_lock);
201 }
202 EXPORT_SYMBOL_GPL(typec_mux_unregister);
203