xref: /openbmc/linux/drivers/usb/typec/mux.c (revision 1f0214a8)
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/property.h>
15 #include <linux/slab.h>
16 
17 #include "class.h"
18 #include "mux.h"
19 
20 static int switch_fwnode_match(struct device *dev, const void *fwnode)
21 {
22 	if (!is_typec_switch(dev))
23 		return 0;
24 
25 	return dev_fwnode(dev) == fwnode;
26 }
27 
28 static void *typec_switch_match(struct fwnode_handle *fwnode, const char *id,
29 				void *data)
30 {
31 	struct device *dev;
32 
33 	/*
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.
40 	 */
41 	if (id && !fwnode_property_present(fwnode, id))
42 		return NULL;
43 
44 	/*
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.
48 	 */
49 	dev = class_find_device(&typec_mux_class, NULL, fwnode,
50 				switch_fwnode_match);
51 
52 	return dev ? to_typec_switch(dev) : ERR_PTR(-EPROBE_DEFER);
53 }
54 
55 /**
56  * fwnode_typec_switch_get - Find USB Type-C orientation switch
57  * @fwnode: The caller device node
58  *
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.
63  */
64 struct typec_switch *fwnode_typec_switch_get(struct fwnode_handle *fwnode)
65 {
66 	struct typec_switch *sw;
67 
68 	sw = fwnode_connection_find_match(fwnode, "orientation-switch", NULL,
69 					  typec_switch_match);
70 	if (!IS_ERR_OR_NULL(sw))
71 		WARN_ON(!try_module_get(sw->dev.parent->driver->owner));
72 
73 	return sw;
74 }
75 EXPORT_SYMBOL_GPL(fwnode_typec_switch_get);
76 
77 /**
78  * typec_switch_put - Release USB Type-C orientation switch
79  * @sw: USB Type-C orientation switch
80  *
81  * Decrement reference count for @sw.
82  */
83 void typec_switch_put(struct typec_switch *sw)
84 {
85 	if (!IS_ERR_OR_NULL(sw)) {
86 		module_put(sw->dev.parent->driver->owner);
87 		put_device(&sw->dev);
88 	}
89 }
90 EXPORT_SYMBOL_GPL(typec_switch_put);
91 
92 static void typec_switch_release(struct device *dev)
93 {
94 	kfree(to_typec_switch(dev));
95 }
96 
97 const struct device_type typec_switch_dev_type = {
98 	.name = "orientation_switch",
99 	.release = typec_switch_release,
100 };
101 
102 /**
103  * typec_switch_register - Register USB Type-C orientation switch
104  * @parent: Parent device
105  * @desc: Orientation switch description
106  *
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.
111  */
112 struct typec_switch *
113 typec_switch_register(struct device *parent,
114 		      const struct typec_switch_desc *desc)
115 {
116 	struct typec_switch *sw;
117 	int ret;
118 
119 	if (!desc || !desc->set)
120 		return ERR_PTR(-EINVAL);
121 
122 	sw = kzalloc(sizeof(*sw), GFP_KERNEL);
123 	if (!sw)
124 		return ERR_PTR(-ENOMEM);
125 
126 	sw->set = desc->set;
127 
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 	dev_set_name(&sw->dev, "%s-switch",
135 		     desc->name ? desc->name : dev_name(parent));
136 
137 	ret = device_add(&sw->dev);
138 	if (ret) {
139 		dev_err(parent, "failed to register switch (%d)\n", ret);
140 		put_device(&sw->dev);
141 		return ERR_PTR(ret);
142 	}
143 
144 	return sw;
145 }
146 EXPORT_SYMBOL_GPL(typec_switch_register);
147 
148 int typec_switch_set(struct typec_switch *sw,
149 		     enum typec_orientation orientation)
150 {
151 	if (IS_ERR_OR_NULL(sw))
152 		return 0;
153 
154 	return sw->set(sw, orientation);
155 }
156 EXPORT_SYMBOL_GPL(typec_switch_set);
157 
158 /**
159  * typec_switch_unregister - Unregister USB Type-C orientation switch
160  * @sw: USB Type-C orientation switch
161  *
162  * Unregister switch that was registered with typec_switch_register().
163  */
164 void typec_switch_unregister(struct typec_switch *sw)
165 {
166 	if (!IS_ERR_OR_NULL(sw))
167 		device_unregister(&sw->dev);
168 }
169 EXPORT_SYMBOL_GPL(typec_switch_unregister);
170 
171 void typec_switch_set_drvdata(struct typec_switch *sw, void *data)
172 {
173 	dev_set_drvdata(&sw->dev, data);
174 }
175 EXPORT_SYMBOL_GPL(typec_switch_set_drvdata);
176 
177 void *typec_switch_get_drvdata(struct typec_switch *sw)
178 {
179 	return dev_get_drvdata(&sw->dev);
180 }
181 EXPORT_SYMBOL_GPL(typec_switch_get_drvdata);
182 
183 /* ------------------------------------------------------------------------- */
184 
185 static int mux_fwnode_match(struct device *dev, const void *fwnode)
186 {
187 	if (!is_typec_mux(dev))
188 		return 0;
189 
190 	return dev_fwnode(dev) == fwnode;
191 }
192 
193 static void *typec_mux_match(struct fwnode_handle *fwnode, const char *id,
194 			     void *data)
195 {
196 	const struct typec_altmode_desc *desc = data;
197 	struct device *dev;
198 	bool match;
199 	int nval;
200 	u16 *val;
201 	int ret;
202 	int i;
203 
204 	/*
205 	 * Check has the identifier already been "consumed". If it
206 	 * has, no need to do any extra connection identification.
207 	 */
208 	match = !id;
209 	if (match)
210 		goto find_mux;
211 
212 	/* Accessory Mode muxes */
213 	if (!desc) {
214 		match = fwnode_property_present(fwnode, "accessory");
215 		if (match)
216 			goto find_mux;
217 		return NULL;
218 	}
219 
220 	/* Alternate Mode muxes */
221 	nval = fwnode_property_count_u16(fwnode, "svid");
222 	if (nval <= 0)
223 		return NULL;
224 
225 	val = kcalloc(nval, sizeof(*val), GFP_KERNEL);
226 	if (!val)
227 		return ERR_PTR(-ENOMEM);
228 
229 	ret = fwnode_property_read_u16_array(fwnode, "svid", val, nval);
230 	if (ret < 0) {
231 		kfree(val);
232 		return ERR_PTR(ret);
233 	}
234 
235 	for (i = 0; i < nval; i++) {
236 		match = val[i] == desc->svid;
237 		if (match) {
238 			kfree(val);
239 			goto find_mux;
240 		}
241 	}
242 	kfree(val);
243 	return NULL;
244 
245 find_mux:
246 	dev = class_find_device(&typec_mux_class, NULL, fwnode,
247 				mux_fwnode_match);
248 
249 	return dev ? to_typec_mux(dev) : ERR_PTR(-EPROBE_DEFER);
250 }
251 
252 /**
253  * fwnode_typec_mux_get - Find USB Type-C Multiplexer
254  * @fwnode: The caller device node
255  * @desc: Alt Mode description
256  *
257  * Finds a mux linked to the caller. This function is primarily meant for the
258  * Type-C drivers. Returns a reference to the mux on success, NULL if no
259  * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection
260  * was found but the mux has not been enumerated yet.
261  */
262 struct typec_mux *fwnode_typec_mux_get(struct fwnode_handle *fwnode,
263 				       const struct typec_altmode_desc *desc)
264 {
265 	struct typec_mux *mux;
266 
267 	mux = fwnode_connection_find_match(fwnode, "mode-switch", (void *)desc,
268 					   typec_mux_match);
269 	if (!IS_ERR_OR_NULL(mux))
270 		WARN_ON(!try_module_get(mux->dev.parent->driver->owner));
271 
272 	return mux;
273 }
274 EXPORT_SYMBOL_GPL(fwnode_typec_mux_get);
275 
276 /**
277  * typec_mux_put - Release handle to a Multiplexer
278  * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
279  *
280  * Decrements reference count for @mux.
281  */
282 void typec_mux_put(struct typec_mux *mux)
283 {
284 	if (!IS_ERR_OR_NULL(mux)) {
285 		module_put(mux->dev.parent->driver->owner);
286 		put_device(&mux->dev);
287 	}
288 }
289 EXPORT_SYMBOL_GPL(typec_mux_put);
290 
291 int typec_mux_set(struct typec_mux *mux, struct typec_mux_state *state)
292 {
293 	if (IS_ERR_OR_NULL(mux))
294 		return 0;
295 
296 	return mux->set(mux, state);
297 }
298 EXPORT_SYMBOL_GPL(typec_mux_set);
299 
300 static void typec_mux_release(struct device *dev)
301 {
302 	kfree(to_typec_mux(dev));
303 }
304 
305 const struct device_type typec_mux_dev_type = {
306 	.name = "mode_switch",
307 	.release = typec_mux_release,
308 };
309 
310 /**
311  * typec_mux_register - Register Multiplexer routing USB Type-C pins
312  * @parent: Parent device
313  * @desc: Multiplexer description
314  *
315  * USB Type-C connectors can be used for alternate modes of operation besides
316  * USB when Accessory/Alternate Modes are supported. With some of those modes,
317  * the pins on the connector need to be reconfigured. This function registers
318  * multiplexer switches routing the pins on the connector.
319  */
320 struct typec_mux *
321 typec_mux_register(struct device *parent, const struct typec_mux_desc *desc)
322 {
323 	struct typec_mux *mux;
324 	int ret;
325 
326 	if (!desc || !desc->set)
327 		return ERR_PTR(-EINVAL);
328 
329 	mux = kzalloc(sizeof(*mux), GFP_KERNEL);
330 	if (!mux)
331 		return ERR_PTR(-ENOMEM);
332 
333 	mux->set = desc->set;
334 
335 	device_initialize(&mux->dev);
336 	mux->dev.parent = parent;
337 	mux->dev.fwnode = desc->fwnode;
338 	mux->dev.class = &typec_mux_class;
339 	mux->dev.type = &typec_mux_dev_type;
340 	mux->dev.driver_data = desc->drvdata;
341 	dev_set_name(&mux->dev, "%s-mux",
342 		     desc->name ? desc->name : dev_name(parent));
343 
344 	ret = device_add(&mux->dev);
345 	if (ret) {
346 		dev_err(parent, "failed to register mux (%d)\n", ret);
347 		put_device(&mux->dev);
348 		return ERR_PTR(ret);
349 	}
350 
351 	return mux;
352 }
353 EXPORT_SYMBOL_GPL(typec_mux_register);
354 
355 /**
356  * typec_mux_unregister - Unregister Multiplexer Switch
357  * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
358  *
359  * Unregister mux that was registered with typec_mux_register().
360  */
361 void typec_mux_unregister(struct typec_mux *mux)
362 {
363 	if (!IS_ERR_OR_NULL(mux))
364 		device_unregister(&mux->dev);
365 }
366 EXPORT_SYMBOL_GPL(typec_mux_unregister);
367 
368 void typec_mux_set_drvdata(struct typec_mux *mux, void *data)
369 {
370 	dev_set_drvdata(&mux->dev, data);
371 }
372 EXPORT_SYMBOL_GPL(typec_mux_set_drvdata);
373 
374 void *typec_mux_get_drvdata(struct typec_mux *mux)
375 {
376 	return dev_get_drvdata(&mux->dev);
377 }
378 EXPORT_SYMBOL_GPL(typec_mux_get_drvdata);
379 
380 struct class typec_mux_class = {
381 	.name = "typec_mux",
382 	.owner = THIS_MODULE,
383 };
384