xref: /openbmc/linux/drivers/usb/typec/mux.c (revision 3e3b8196)
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, "typec-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  * @name: Mux identifier
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, const char *name)
139 {
140 	struct typec_mux *mux;
141 
142 	mutex_lock(&mux_lock);
143 	mux = device_connection_find_match(dev, name, NULL, typec_mux_match);
144 	if (!IS_ERR_OR_NULL(mux)) {
145 		WARN_ON(!try_module_get(mux->dev->driver->owner));
146 		get_device(mux->dev);
147 	}
148 	mutex_unlock(&mux_lock);
149 
150 	return mux;
151 }
152 EXPORT_SYMBOL_GPL(typec_mux_get);
153 
154 /**
155  * typec_mux_put - Release handle to a Multiplexer
156  * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
157  *
158  * Decrements reference count for @mux.
159  */
160 void typec_mux_put(struct typec_mux *mux)
161 {
162 	if (!IS_ERR_OR_NULL(mux)) {
163 		module_put(mux->dev->driver->owner);
164 		put_device(mux->dev);
165 	}
166 }
167 EXPORT_SYMBOL_GPL(typec_mux_put);
168 
169 /**
170  * typec_mux_register - Register Multiplexer routing USB Type-C pins
171  * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
172  *
173  * USB Type-C connectors can be used for alternate modes of operation besides
174  * USB when Accessory/Alternate Modes are supported. With some of those modes,
175  * the pins on the connector need to be reconfigured. This function registers
176  * multiplexer switches routing the pins on the connector.
177  */
178 int typec_mux_register(struct typec_mux *mux)
179 {
180 	mutex_lock(&mux_lock);
181 	list_add_tail(&mux->entry, &mux_list);
182 	mutex_unlock(&mux_lock);
183 
184 	return 0;
185 }
186 EXPORT_SYMBOL_GPL(typec_mux_register);
187 
188 /**
189  * typec_mux_unregister - Unregister Multiplexer Switch
190  * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
191  *
192  * Unregister mux that was registered with typec_mux_register().
193  */
194 void typec_mux_unregister(struct typec_mux *mux)
195 {
196 	mutex_lock(&mux_lock);
197 	list_del(&mux->entry);
198 	mutex_unlock(&mux_lock);
199 }
200 EXPORT_SYMBOL_GPL(typec_mux_unregister);
201