treewide: remove redundant IS_ERR() before error code check
[linux/fpc-iii.git] / drivers / usb / typec / mux.c
blob5baf0f416c73c6016080eac0af64715753446ba4
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * USB Type-C Multiplexer/DeMultiplexer Switch support
5 * Copyright (C) 2018 Intel Corporation
6 * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
7 * Hans de Goede <hdegoede@redhat.com>
8 */
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 #include <linux/usb/typec_mux.h>
18 #include "bus.h"
20 static int name_match(struct device *dev, const void *name)
22 return !strcmp((const char *)name, dev_name(dev));
25 static bool dev_name_ends_with(struct device *dev, const char *suffix)
27 const char *name = dev_name(dev);
28 const int name_len = strlen(name);
29 const int suffix_len = strlen(suffix);
31 if (suffix_len > name_len)
32 return false;
34 return strcmp(name + (name_len - suffix_len), suffix) == 0;
37 static int switch_fwnode_match(struct device *dev, const void *fwnode)
39 return dev_fwnode(dev) == fwnode && dev_name_ends_with(dev, "-switch");
42 static void *typec_switch_match(struct device_connection *con, int ep,
43 void *data)
45 struct device *dev;
47 if (con->fwnode) {
48 if (con->id && !fwnode_property_present(con->fwnode, con->id))
49 return NULL;
51 dev = class_find_device(&typec_mux_class, NULL, con->fwnode,
52 switch_fwnode_match);
53 } else {
54 dev = class_find_device(&typec_mux_class, NULL,
55 con->endpoint[ep], name_match);
58 return dev ? to_typec_switch(dev) : ERR_PTR(-EPROBE_DEFER);
61 /**
62 * typec_switch_get - Find USB Type-C orientation switch
63 * @dev: The caller device
65 * Finds a switch linked with @dev. Returns a reference to the switch on
66 * success, NULL if no matching connection was found, or
67 * ERR_PTR(-EPROBE_DEFER) when a connection was found but the switch
68 * has not been enumerated yet.
70 struct typec_switch *typec_switch_get(struct device *dev)
72 struct typec_switch *sw;
74 sw = device_connection_find_match(dev, "orientation-switch", NULL,
75 typec_switch_match);
76 if (!IS_ERR_OR_NULL(sw))
77 WARN_ON(!try_module_get(sw->dev.parent->driver->owner));
79 return sw;
81 EXPORT_SYMBOL_GPL(typec_switch_get);
83 /**
84 * typec_put_switch - Release USB Type-C orientation switch
85 * @sw: USB Type-C orientation switch
87 * Decrement reference count for @sw.
89 void typec_switch_put(struct typec_switch *sw)
91 if (!IS_ERR_OR_NULL(sw)) {
92 module_put(sw->dev.parent->driver->owner);
93 put_device(&sw->dev);
96 EXPORT_SYMBOL_GPL(typec_switch_put);
98 static void typec_switch_release(struct device *dev)
100 kfree(to_typec_switch(dev));
103 static const struct device_type typec_switch_dev_type = {
104 .name = "orientation_switch",
105 .release = typec_switch_release,
109 * typec_switch_register - Register USB Type-C orientation switch
110 * @parent: Parent device
111 * @desc: Orientation switch description
113 * This function registers a switch that can be used for routing the correct
114 * data pairs depending on the cable plug orientation from the USB Type-C
115 * connector to the USB controllers. USB Type-C plugs can be inserted
116 * right-side-up or upside-down.
118 struct typec_switch *
119 typec_switch_register(struct device *parent,
120 const struct typec_switch_desc *desc)
122 struct typec_switch *sw;
123 int ret;
125 if (!desc || !desc->set)
126 return ERR_PTR(-EINVAL);
128 sw = kzalloc(sizeof(*sw), GFP_KERNEL);
129 if (!sw)
130 return ERR_PTR(-ENOMEM);
132 sw->set = desc->set;
134 device_initialize(&sw->dev);
135 sw->dev.parent = parent;
136 sw->dev.fwnode = desc->fwnode;
137 sw->dev.class = &typec_mux_class;
138 sw->dev.type = &typec_switch_dev_type;
139 sw->dev.driver_data = desc->drvdata;
140 dev_set_name(&sw->dev, "%s-switch", dev_name(parent));
142 ret = device_add(&sw->dev);
143 if (ret) {
144 dev_err(parent, "failed to register switch (%d)\n", ret);
145 put_device(&sw->dev);
146 return ERR_PTR(ret);
149 return sw;
151 EXPORT_SYMBOL_GPL(typec_switch_register);
154 * typec_switch_unregister - Unregister USB Type-C orientation switch
155 * @sw: USB Type-C orientation switch
157 * Unregister switch that was registered with typec_switch_register().
159 void typec_switch_unregister(struct typec_switch *sw)
161 if (!IS_ERR_OR_NULL(sw))
162 device_unregister(&sw->dev);
164 EXPORT_SYMBOL_GPL(typec_switch_unregister);
166 void typec_switch_set_drvdata(struct typec_switch *sw, void *data)
168 dev_set_drvdata(&sw->dev, data);
170 EXPORT_SYMBOL_GPL(typec_switch_set_drvdata);
172 void *typec_switch_get_drvdata(struct typec_switch *sw)
174 return dev_get_drvdata(&sw->dev);
176 EXPORT_SYMBOL_GPL(typec_switch_get_drvdata);
178 /* ------------------------------------------------------------------------- */
180 static int mux_fwnode_match(struct device *dev, const void *fwnode)
182 return dev_fwnode(dev) == fwnode && dev_name_ends_with(dev, "-mux");
185 static void *typec_mux_match(struct device_connection *con, int ep, void *data)
187 const struct typec_altmode_desc *desc = data;
188 struct device *dev;
189 bool match;
190 int nval;
191 u16 *val;
192 int i;
194 if (!con->fwnode) {
195 dev = class_find_device(&typec_mux_class, NULL,
196 con->endpoint[ep], name_match);
198 return dev ? to_typec_switch(dev) : ERR_PTR(-EPROBE_DEFER);
202 * Check has the identifier already been "consumed". If it
203 * has, no need to do any extra connection identification.
205 match = !con->id;
206 if (match)
207 goto find_mux;
209 /* Accessory Mode muxes */
210 if (!desc) {
211 match = fwnode_property_present(con->fwnode, "accessory");
212 if (match)
213 goto find_mux;
214 return NULL;
217 /* Alternate Mode muxes */
218 nval = fwnode_property_count_u16(con->fwnode, "svid");
219 if (nval <= 0)
220 return NULL;
222 val = kcalloc(nval, sizeof(*val), GFP_KERNEL);
223 if (!val)
224 return ERR_PTR(-ENOMEM);
226 nval = fwnode_property_read_u16_array(con->fwnode, "svid", val, nval);
227 if (nval < 0) {
228 kfree(val);
229 return ERR_PTR(nval);
232 for (i = 0; i < nval; i++) {
233 match = val[i] == desc->svid;
234 if (match) {
235 kfree(val);
236 goto find_mux;
239 kfree(val);
240 return NULL;
242 find_mux:
243 dev = class_find_device(&typec_mux_class, NULL, con->fwnode,
244 mux_fwnode_match);
246 return dev ? to_typec_switch(dev) : ERR_PTR(-EPROBE_DEFER);
250 * typec_mux_get - Find USB Type-C Multiplexer
251 * @dev: The caller device
252 * @desc: Alt Mode description
254 * Finds a mux linked to the caller. This function is primarily meant for the
255 * Type-C drivers. Returns a reference to the mux on success, NULL if no
256 * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection
257 * was found but the mux has not been enumerated yet.
259 struct typec_mux *typec_mux_get(struct device *dev,
260 const struct typec_altmode_desc *desc)
262 struct typec_mux *mux;
264 mux = device_connection_find_match(dev, "mode-switch", (void *)desc,
265 typec_mux_match);
266 if (!IS_ERR_OR_NULL(mux))
267 WARN_ON(!try_module_get(mux->dev.parent->driver->owner));
269 return mux;
271 EXPORT_SYMBOL_GPL(typec_mux_get);
274 * typec_mux_put - Release handle to a Multiplexer
275 * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
277 * Decrements reference count for @mux.
279 void typec_mux_put(struct typec_mux *mux)
281 if (!IS_ERR_OR_NULL(mux)) {
282 module_put(mux->dev.parent->driver->owner);
283 put_device(&mux->dev);
286 EXPORT_SYMBOL_GPL(typec_mux_put);
288 static void typec_mux_release(struct device *dev)
290 kfree(to_typec_mux(dev));
293 static const struct device_type typec_mux_dev_type = {
294 .name = "mode_switch",
295 .release = typec_mux_release,
299 * typec_mux_register - Register Multiplexer routing USB Type-C pins
300 * @parent: Parent device
301 * @desc: Multiplexer description
303 * USB Type-C connectors can be used for alternate modes of operation besides
304 * USB when Accessory/Alternate Modes are supported. With some of those modes,
305 * the pins on the connector need to be reconfigured. This function registers
306 * multiplexer switches routing the pins on the connector.
308 struct typec_mux *
309 typec_mux_register(struct device *parent, const struct typec_mux_desc *desc)
311 struct typec_mux *mux;
312 int ret;
314 if (!desc || !desc->set)
315 return ERR_PTR(-EINVAL);
317 mux = kzalloc(sizeof(*mux), GFP_KERNEL);
318 if (!mux)
319 return ERR_PTR(-ENOMEM);
321 mux->set = desc->set;
323 device_initialize(&mux->dev);
324 mux->dev.parent = parent;
325 mux->dev.fwnode = desc->fwnode;
326 mux->dev.class = &typec_mux_class;
327 mux->dev.type = &typec_mux_dev_type;
328 mux->dev.driver_data = desc->drvdata;
329 dev_set_name(&mux->dev, "%s-mux", dev_name(parent));
331 ret = device_add(&mux->dev);
332 if (ret) {
333 dev_err(parent, "failed to register mux (%d)\n", ret);
334 put_device(&mux->dev);
335 return ERR_PTR(ret);
338 return mux;
340 EXPORT_SYMBOL_GPL(typec_mux_register);
343 * typec_mux_unregister - Unregister Multiplexer Switch
344 * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
346 * Unregister mux that was registered with typec_mux_register().
348 void typec_mux_unregister(struct typec_mux *mux)
350 if (!IS_ERR_OR_NULL(mux))
351 device_unregister(&mux->dev);
353 EXPORT_SYMBOL_GPL(typec_mux_unregister);
355 void typec_mux_set_drvdata(struct typec_mux *mux, void *data)
357 dev_set_drvdata(&mux->dev, data);
359 EXPORT_SYMBOL_GPL(typec_mux_set_drvdata);
361 void *typec_mux_get_drvdata(struct typec_mux *mux)
363 return dev_get_drvdata(&mux->dev);
365 EXPORT_SYMBOL_GPL(typec_mux_get_drvdata);
367 struct class typec_mux_class = {
368 .name = "typec_mux",
369 .owner = THIS_MODULE,