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 | #define TYPEC_MUX_MAX_DEVS 3 |
21 | |
22 | struct typec_switch { |
23 | struct typec_switch_dev *sw_devs[TYPEC_MUX_MAX_DEVS]; |
24 | unsigned int num_sw_devs; |
25 | }; |
26 | |
27 | static int switch_fwnode_match(struct device *dev, const void *fwnode) |
28 | { |
29 | if (!is_typec_switch_dev(dev)) |
30 | return 0; |
31 | |
32 | return device_match_fwnode(dev, fwnode); |
33 | } |
34 | |
35 | static void *typec_switch_match(const struct fwnode_handle *fwnode, |
36 | const char *id, void *data) |
37 | { |
38 | struct device *dev; |
39 | |
40 | /* |
41 | * Device graph (OF graph) does not give any means to identify the |
42 | * device type or the device class of the remote port parent that @fwnode |
43 | * represents, so in order to identify the type or the class of @fwnode |
44 | * an additional device property is needed. With typec switches the |
45 | * property is named "orientation-switch" (@id). The value of the device |
46 | * property is ignored. |
47 | */ |
48 | if (id && !fwnode_property_present(fwnode, propname: id)) |
49 | return NULL; |
50 | |
51 | /* |
52 | * At this point we are sure that @fwnode is a typec switch in all |
53 | * cases. If the switch hasn't yet been registered for some reason, the |
54 | * function "defers probe" for now. |
55 | */ |
56 | dev = class_find_device(class: &typec_mux_class, NULL, data: fwnode, |
57 | match: switch_fwnode_match); |
58 | |
59 | return dev ? to_typec_switch_dev(dev) : ERR_PTR(error: -EPROBE_DEFER); |
60 | } |
61 | |
62 | /** |
63 | * fwnode_typec_switch_get - Find USB Type-C orientation switch |
64 | * @fwnode: The caller device node |
65 | * |
66 | * Finds a switch linked with @dev. Returns a reference to the switch on |
67 | * success, NULL if no matching connection was found, or |
68 | * ERR_PTR(-EPROBE_DEFER) when a connection was found but the switch |
69 | * has not been enumerated yet. |
70 | */ |
71 | struct typec_switch *fwnode_typec_switch_get(struct fwnode_handle *fwnode) |
72 | { |
73 | struct typec_switch_dev *sw_devs[TYPEC_MUX_MAX_DEVS]; |
74 | struct typec_switch *sw; |
75 | int count; |
76 | int err; |
77 | int i; |
78 | |
79 | sw = kzalloc(size: sizeof(*sw), GFP_KERNEL); |
80 | if (!sw) |
81 | return ERR_PTR(error: -ENOMEM); |
82 | |
83 | count = fwnode_connection_find_matches(fwnode, con_id: "orientation-switch" , NULL, |
84 | match: typec_switch_match, |
85 | matches: (void **)sw_devs, |
86 | ARRAY_SIZE(sw_devs)); |
87 | if (count <= 0) { |
88 | kfree(objp: sw); |
89 | return NULL; |
90 | } |
91 | |
92 | for (i = 0; i < count; i++) { |
93 | if (IS_ERR(ptr: sw_devs[i])) { |
94 | err = PTR_ERR(ptr: sw_devs[i]); |
95 | goto put_sw_devs; |
96 | } |
97 | } |
98 | |
99 | for (i = 0; i < count; i++) { |
100 | WARN_ON(!try_module_get(sw_devs[i]->dev.parent->driver->owner)); |
101 | sw->sw_devs[i] = sw_devs[i]; |
102 | } |
103 | |
104 | sw->num_sw_devs = count; |
105 | |
106 | return sw; |
107 | |
108 | put_sw_devs: |
109 | for (i = 0; i < count; i++) { |
110 | if (!IS_ERR(ptr: sw_devs[i])) |
111 | put_device(dev: &sw_devs[i]->dev); |
112 | } |
113 | |
114 | kfree(objp: sw); |
115 | |
116 | return ERR_PTR(error: err); |
117 | } |
118 | EXPORT_SYMBOL_GPL(fwnode_typec_switch_get); |
119 | |
120 | /** |
121 | * typec_switch_put - Release USB Type-C orientation switch |
122 | * @sw: USB Type-C orientation switch |
123 | * |
124 | * Decrement reference count for @sw. |
125 | */ |
126 | void typec_switch_put(struct typec_switch *sw) |
127 | { |
128 | struct typec_switch_dev *sw_dev; |
129 | unsigned int i; |
130 | |
131 | if (IS_ERR_OR_NULL(ptr: sw)) |
132 | return; |
133 | |
134 | for (i = 0; i < sw->num_sw_devs; i++) { |
135 | sw_dev = sw->sw_devs[i]; |
136 | |
137 | module_put(module: sw_dev->dev.parent->driver->owner); |
138 | put_device(dev: &sw_dev->dev); |
139 | } |
140 | kfree(objp: sw); |
141 | } |
142 | EXPORT_SYMBOL_GPL(typec_switch_put); |
143 | |
144 | static void typec_switch_release(struct device *dev) |
145 | { |
146 | kfree(to_typec_switch_dev(dev)); |
147 | } |
148 | |
149 | const struct device_type typec_switch_dev_type = { |
150 | .name = "orientation_switch" , |
151 | .release = typec_switch_release, |
152 | }; |
153 | |
154 | /** |
155 | * typec_switch_register - Register USB Type-C orientation switch |
156 | * @parent: Parent device |
157 | * @desc: Orientation switch description |
158 | * |
159 | * This function registers a switch that can be used for routing the correct |
160 | * data pairs depending on the cable plug orientation from the USB Type-C |
161 | * connector to the USB controllers. USB Type-C plugs can be inserted |
162 | * right-side-up or upside-down. |
163 | */ |
164 | struct typec_switch_dev * |
165 | typec_switch_register(struct device *parent, |
166 | const struct typec_switch_desc *desc) |
167 | { |
168 | struct typec_switch_dev *sw_dev; |
169 | int ret; |
170 | |
171 | if (!desc || !desc->set) |
172 | return ERR_PTR(error: -EINVAL); |
173 | |
174 | sw_dev = kzalloc(size: sizeof(*sw_dev), GFP_KERNEL); |
175 | if (!sw_dev) |
176 | return ERR_PTR(error: -ENOMEM); |
177 | |
178 | sw_dev->set = desc->set; |
179 | |
180 | device_initialize(dev: &sw_dev->dev); |
181 | sw_dev->dev.parent = parent; |
182 | sw_dev->dev.fwnode = desc->fwnode; |
183 | sw_dev->dev.class = &typec_mux_class; |
184 | sw_dev->dev.type = &typec_switch_dev_type; |
185 | sw_dev->dev.driver_data = desc->drvdata; |
186 | ret = dev_set_name(dev: &sw_dev->dev, name: "%s-switch" , desc->name ? desc->name : dev_name(dev: parent)); |
187 | if (ret) { |
188 | put_device(dev: &sw_dev->dev); |
189 | return ERR_PTR(error: ret); |
190 | } |
191 | |
192 | ret = device_add(dev: &sw_dev->dev); |
193 | if (ret) { |
194 | dev_err(parent, "failed to register switch (%d)\n" , ret); |
195 | put_device(dev: &sw_dev->dev); |
196 | return ERR_PTR(error: ret); |
197 | } |
198 | |
199 | return sw_dev; |
200 | } |
201 | EXPORT_SYMBOL_GPL(typec_switch_register); |
202 | |
203 | int typec_switch_set(struct typec_switch *sw, |
204 | enum typec_orientation orientation) |
205 | { |
206 | struct typec_switch_dev *sw_dev; |
207 | unsigned int i; |
208 | int ret; |
209 | |
210 | if (IS_ERR_OR_NULL(ptr: sw)) |
211 | return 0; |
212 | |
213 | for (i = 0; i < sw->num_sw_devs; i++) { |
214 | sw_dev = sw->sw_devs[i]; |
215 | |
216 | ret = sw_dev->set(sw_dev, orientation); |
217 | if (ret) |
218 | return ret; |
219 | } |
220 | |
221 | return 0; |
222 | } |
223 | EXPORT_SYMBOL_GPL(typec_switch_set); |
224 | |
225 | /** |
226 | * typec_switch_unregister - Unregister USB Type-C orientation switch |
227 | * @sw_dev: USB Type-C orientation switch |
228 | * |
229 | * Unregister switch that was registered with typec_switch_register(). |
230 | */ |
231 | void typec_switch_unregister(struct typec_switch_dev *sw_dev) |
232 | { |
233 | if (!IS_ERR_OR_NULL(ptr: sw_dev)) |
234 | device_unregister(dev: &sw_dev->dev); |
235 | } |
236 | EXPORT_SYMBOL_GPL(typec_switch_unregister); |
237 | |
238 | void typec_switch_set_drvdata(struct typec_switch_dev *sw_dev, void *data) |
239 | { |
240 | dev_set_drvdata(dev: &sw_dev->dev, data); |
241 | } |
242 | EXPORT_SYMBOL_GPL(typec_switch_set_drvdata); |
243 | |
244 | void *typec_switch_get_drvdata(struct typec_switch_dev *sw_dev) |
245 | { |
246 | return dev_get_drvdata(dev: &sw_dev->dev); |
247 | } |
248 | EXPORT_SYMBOL_GPL(typec_switch_get_drvdata); |
249 | |
250 | /* ------------------------------------------------------------------------- */ |
251 | |
252 | struct typec_mux { |
253 | struct typec_mux_dev *mux_devs[TYPEC_MUX_MAX_DEVS]; |
254 | unsigned int num_mux_devs; |
255 | }; |
256 | |
257 | static int mux_fwnode_match(struct device *dev, const void *fwnode) |
258 | { |
259 | if (!is_typec_mux_dev(dev)) |
260 | return 0; |
261 | |
262 | return device_match_fwnode(dev, fwnode); |
263 | } |
264 | |
265 | static void *typec_mux_match(const struct fwnode_handle *fwnode, |
266 | const char *id, void *data) |
267 | { |
268 | struct device *dev; |
269 | |
270 | /* |
271 | * Device graph (OF graph) does not give any means to identify the |
272 | * device type or the device class of the remote port parent that @fwnode |
273 | * represents, so in order to identify the type or the class of @fwnode |
274 | * an additional device property is needed. With typec muxes the |
275 | * property is named "mode-switch" (@id). The value of the device |
276 | * property is ignored. |
277 | */ |
278 | if (id && !fwnode_property_present(fwnode, propname: id)) |
279 | return NULL; |
280 | |
281 | dev = class_find_device(class: &typec_mux_class, NULL, data: fwnode, |
282 | match: mux_fwnode_match); |
283 | |
284 | return dev ? to_typec_mux_dev(dev) : ERR_PTR(error: -EPROBE_DEFER); |
285 | } |
286 | |
287 | /** |
288 | * fwnode_typec_mux_get - Find USB Type-C Multiplexer |
289 | * @fwnode: The caller device node |
290 | * |
291 | * Finds a mux linked to the caller. This function is primarily meant for the |
292 | * Type-C drivers. Returns a reference to the mux on success, NULL if no |
293 | * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection |
294 | * was found but the mux has not been enumerated yet. |
295 | */ |
296 | struct typec_mux *fwnode_typec_mux_get(struct fwnode_handle *fwnode) |
297 | { |
298 | struct typec_mux_dev *mux_devs[TYPEC_MUX_MAX_DEVS]; |
299 | struct typec_mux *mux; |
300 | int count; |
301 | int err; |
302 | int i; |
303 | |
304 | mux = kzalloc(size: sizeof(*mux), GFP_KERNEL); |
305 | if (!mux) |
306 | return ERR_PTR(error: -ENOMEM); |
307 | |
308 | count = fwnode_connection_find_matches(fwnode, con_id: "mode-switch" , |
309 | NULL, match: typec_mux_match, |
310 | matches: (void **)mux_devs, |
311 | ARRAY_SIZE(mux_devs)); |
312 | if (count <= 0) { |
313 | kfree(objp: mux); |
314 | return NULL; |
315 | } |
316 | |
317 | for (i = 0; i < count; i++) { |
318 | if (IS_ERR(ptr: mux_devs[i])) { |
319 | err = PTR_ERR(ptr: mux_devs[i]); |
320 | goto put_mux_devs; |
321 | } |
322 | } |
323 | |
324 | for (i = 0; i < count; i++) { |
325 | WARN_ON(!try_module_get(mux_devs[i]->dev.parent->driver->owner)); |
326 | mux->mux_devs[i] = mux_devs[i]; |
327 | } |
328 | |
329 | mux->num_mux_devs = count; |
330 | |
331 | return mux; |
332 | |
333 | put_mux_devs: |
334 | for (i = 0; i < count; i++) { |
335 | if (!IS_ERR(ptr: mux_devs[i])) |
336 | put_device(dev: &mux_devs[i]->dev); |
337 | } |
338 | |
339 | kfree(objp: mux); |
340 | |
341 | return ERR_PTR(error: err); |
342 | } |
343 | EXPORT_SYMBOL_GPL(fwnode_typec_mux_get); |
344 | |
345 | /** |
346 | * typec_mux_put - Release handle to a Multiplexer |
347 | * @mux: USB Type-C Connector Multiplexer/DeMultiplexer |
348 | * |
349 | * Decrements reference count for @mux. |
350 | */ |
351 | void typec_mux_put(struct typec_mux *mux) |
352 | { |
353 | struct typec_mux_dev *mux_dev; |
354 | unsigned int i; |
355 | |
356 | if (IS_ERR_OR_NULL(ptr: mux)) |
357 | return; |
358 | |
359 | for (i = 0; i < mux->num_mux_devs; i++) { |
360 | mux_dev = mux->mux_devs[i]; |
361 | module_put(module: mux_dev->dev.parent->driver->owner); |
362 | put_device(dev: &mux_dev->dev); |
363 | } |
364 | kfree(objp: mux); |
365 | } |
366 | EXPORT_SYMBOL_GPL(typec_mux_put); |
367 | |
368 | int typec_mux_set(struct typec_mux *mux, struct typec_mux_state *state) |
369 | { |
370 | struct typec_mux_dev *mux_dev; |
371 | unsigned int i; |
372 | int ret; |
373 | |
374 | if (IS_ERR_OR_NULL(ptr: mux)) |
375 | return 0; |
376 | |
377 | for (i = 0; i < mux->num_mux_devs; i++) { |
378 | mux_dev = mux->mux_devs[i]; |
379 | |
380 | ret = mux_dev->set(mux_dev, state); |
381 | if (ret) |
382 | return ret; |
383 | } |
384 | |
385 | return 0; |
386 | } |
387 | EXPORT_SYMBOL_GPL(typec_mux_set); |
388 | |
389 | static void typec_mux_release(struct device *dev) |
390 | { |
391 | kfree(to_typec_mux_dev(dev)); |
392 | } |
393 | |
394 | const struct device_type typec_mux_dev_type = { |
395 | .name = "mode_switch" , |
396 | .release = typec_mux_release, |
397 | }; |
398 | |
399 | /** |
400 | * typec_mux_register - Register Multiplexer routing USB Type-C pins |
401 | * @parent: Parent device |
402 | * @desc: Multiplexer description |
403 | * |
404 | * USB Type-C connectors can be used for alternate modes of operation besides |
405 | * USB when Accessory/Alternate Modes are supported. With some of those modes, |
406 | * the pins on the connector need to be reconfigured. This function registers |
407 | * multiplexer switches routing the pins on the connector. |
408 | */ |
409 | struct typec_mux_dev * |
410 | typec_mux_register(struct device *parent, const struct typec_mux_desc *desc) |
411 | { |
412 | struct typec_mux_dev *mux_dev; |
413 | int ret; |
414 | |
415 | if (!desc || !desc->set) |
416 | return ERR_PTR(error: -EINVAL); |
417 | |
418 | mux_dev = kzalloc(size: sizeof(*mux_dev), GFP_KERNEL); |
419 | if (!mux_dev) |
420 | return ERR_PTR(error: -ENOMEM); |
421 | |
422 | mux_dev->set = desc->set; |
423 | |
424 | device_initialize(dev: &mux_dev->dev); |
425 | mux_dev->dev.parent = parent; |
426 | mux_dev->dev.fwnode = desc->fwnode; |
427 | mux_dev->dev.class = &typec_mux_class; |
428 | mux_dev->dev.type = &typec_mux_dev_type; |
429 | mux_dev->dev.driver_data = desc->drvdata; |
430 | ret = dev_set_name(dev: &mux_dev->dev, name: "%s-mux" , desc->name ? desc->name : dev_name(dev: parent)); |
431 | if (ret) { |
432 | put_device(dev: &mux_dev->dev); |
433 | return ERR_PTR(error: ret); |
434 | } |
435 | |
436 | ret = device_add(dev: &mux_dev->dev); |
437 | if (ret) { |
438 | dev_err(parent, "failed to register mux (%d)\n" , ret); |
439 | put_device(dev: &mux_dev->dev); |
440 | return ERR_PTR(error: ret); |
441 | } |
442 | |
443 | return mux_dev; |
444 | } |
445 | EXPORT_SYMBOL_GPL(typec_mux_register); |
446 | |
447 | /** |
448 | * typec_mux_unregister - Unregister Multiplexer Switch |
449 | * @mux_dev: USB Type-C Connector Multiplexer/DeMultiplexer |
450 | * |
451 | * Unregister mux that was registered with typec_mux_register(). |
452 | */ |
453 | void typec_mux_unregister(struct typec_mux_dev *mux_dev) |
454 | { |
455 | if (!IS_ERR_OR_NULL(ptr: mux_dev)) |
456 | device_unregister(dev: &mux_dev->dev); |
457 | } |
458 | EXPORT_SYMBOL_GPL(typec_mux_unregister); |
459 | |
460 | void typec_mux_set_drvdata(struct typec_mux_dev *mux_dev, void *data) |
461 | { |
462 | dev_set_drvdata(dev: &mux_dev->dev, data); |
463 | } |
464 | EXPORT_SYMBOL_GPL(typec_mux_set_drvdata); |
465 | |
466 | void *typec_mux_get_drvdata(struct typec_mux_dev *mux_dev) |
467 | { |
468 | return dev_get_drvdata(dev: &mux_dev->dev); |
469 | } |
470 | EXPORT_SYMBOL_GPL(typec_mux_get_drvdata); |
471 | |
472 | struct class typec_mux_class = { |
473 | .name = "typec_mux" , |
474 | }; |
475 | |