]> Git Repo - linux.git/blob - drivers/usb/typec/mux.c
Merge tag 'arm64-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux
[linux.git] / drivers / usb / typec / mux.c
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 <[email protected]>
7  *         Hans de Goede <[email protected]>
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 #include <linux/usb/typec_mux.h>
17
18 static DEFINE_MUTEX(switch_lock);
19 static DEFINE_MUTEX(mux_lock);
20 static LIST_HEAD(switch_list);
21 static LIST_HEAD(mux_list);
22
23 static void *typec_switch_match(struct device_connection *con, int ep,
24                                 void *data)
25 {
26         struct typec_switch *sw;
27
28         if (!con->fwnode) {
29                 list_for_each_entry(sw, &switch_list, entry)
30                         if (!strcmp(con->endpoint[ep], dev_name(sw->dev)))
31                                 return sw;
32                 return ERR_PTR(-EPROBE_DEFER);
33         }
34
35         /*
36          * With OF graph the mux node must have a boolean device property named
37          * "orientation-switch".
38          */
39         if (con->id && !fwnode_property_present(con->fwnode, con->id))
40                 return NULL;
41
42         list_for_each_entry(sw, &switch_list, entry)
43                 if (dev_fwnode(sw->dev) == con->fwnode)
44                         return sw;
45
46         return con->id ? ERR_PTR(-EPROBE_DEFER) : NULL;
47 }
48
49 /**
50  * typec_switch_get - Find USB Type-C orientation switch
51  * @dev: The caller device
52  *
53  * Finds a switch linked with @dev. Returns a reference to the switch on
54  * success, NULL if no matching connection was found, or
55  * ERR_PTR(-EPROBE_DEFER) when a connection was found but the switch
56  * has not been enumerated yet.
57  */
58 struct typec_switch *typec_switch_get(struct device *dev)
59 {
60         struct typec_switch *sw;
61
62         mutex_lock(&switch_lock);
63         sw = device_connection_find_match(dev, "orientation-switch", NULL,
64                                           typec_switch_match);
65         if (!IS_ERR_OR_NULL(sw)) {
66                 WARN_ON(!try_module_get(sw->dev->driver->owner));
67                 get_device(sw->dev);
68         }
69         mutex_unlock(&switch_lock);
70
71         return sw;
72 }
73 EXPORT_SYMBOL_GPL(typec_switch_get);
74
75 /**
76  * typec_put_switch - Release USB Type-C orientation switch
77  * @sw: USB Type-C orientation switch
78  *
79  * Decrement reference count for @sw.
80  */
81 void typec_switch_put(struct typec_switch *sw)
82 {
83         if (!IS_ERR_OR_NULL(sw)) {
84                 module_put(sw->dev->driver->owner);
85                 put_device(sw->dev);
86         }
87 }
88 EXPORT_SYMBOL_GPL(typec_switch_put);
89
90 /**
91  * typec_switch_register - Register USB Type-C orientation switch
92  * @sw: USB Type-C orientation switch
93  *
94  * This function registers a switch that can be used for routing the correct
95  * data pairs depending on the cable plug orientation from the USB Type-C
96  * connector to the USB controllers. USB Type-C plugs can be inserted
97  * right-side-up or upside-down.
98  */
99 int typec_switch_register(struct typec_switch *sw)
100 {
101         mutex_lock(&switch_lock);
102         list_add_tail(&sw->entry, &switch_list);
103         mutex_unlock(&switch_lock);
104
105         return 0;
106 }
107 EXPORT_SYMBOL_GPL(typec_switch_register);
108
109 /**
110  * typec_switch_unregister - Unregister USB Type-C orientation switch
111  * @sw: USB Type-C orientation switch
112  *
113  * Unregister switch that was registered with typec_switch_register().
114  */
115 void typec_switch_unregister(struct typec_switch *sw)
116 {
117         mutex_lock(&switch_lock);
118         list_del(&sw->entry);
119         mutex_unlock(&switch_lock);
120 }
121 EXPORT_SYMBOL_GPL(typec_switch_unregister);
122
123 /* ------------------------------------------------------------------------- */
124
125 static void *typec_mux_match(struct device_connection *con, int ep, void *data)
126 {
127         const struct typec_altmode_desc *desc = data;
128         struct typec_mux *mux;
129         int nval;
130         bool match;
131         u16 *val;
132         int i;
133
134         if (!con->fwnode) {
135                 list_for_each_entry(mux, &mux_list, entry)
136                         if (!strcmp(con->endpoint[ep], dev_name(mux->dev)))
137                                 return mux;
138                 return ERR_PTR(-EPROBE_DEFER);
139         }
140
141         /*
142          * Check has the identifier already been "consumed". If it
143          * has, no need to do any extra connection identification.
144          */
145         match = !con->id;
146         if (match)
147                 goto find_mux;
148
149         /* Accessory Mode muxes */
150         if (!desc) {
151                 match = fwnode_property_present(con->fwnode, "accessory");
152                 if (match)
153                         goto find_mux;
154                 return NULL;
155         }
156
157         /* Alternate Mode muxes */
158         nval = fwnode_property_read_u16_array(con->fwnode, "svid", NULL, 0);
159         if (nval <= 0)
160                 return NULL;
161
162         val = kcalloc(nval, sizeof(*val), GFP_KERNEL);
163         if (!val)
164                 return ERR_PTR(-ENOMEM);
165
166         nval = fwnode_property_read_u16_array(con->fwnode, "svid", val, nval);
167         if (nval < 0) {
168                 kfree(val);
169                 return ERR_PTR(nval);
170         }
171
172         for (i = 0; i < nval; i++) {
173                 match = val[i] == desc->svid;
174                 if (match) {
175                         kfree(val);
176                         goto find_mux;
177                 }
178         }
179         kfree(val);
180         return NULL;
181
182 find_mux:
183         list_for_each_entry(mux, &mux_list, entry)
184                 if (dev_fwnode(mux->dev) == con->fwnode)
185                         return mux;
186
187         return ERR_PTR(-EPROBE_DEFER);
188 }
189
190 /**
191  * typec_mux_get - Find USB Type-C Multiplexer
192  * @dev: The caller device
193  * @desc: Alt Mode description
194  *
195  * Finds a mux linked to the caller. This function is primarily meant for the
196  * Type-C drivers. Returns a reference to the mux on success, NULL if no
197  * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection
198  * was found but the mux has not been enumerated yet.
199  */
200 struct typec_mux *typec_mux_get(struct device *dev,
201                                 const struct typec_altmode_desc *desc)
202 {
203         struct typec_mux *mux;
204
205         mutex_lock(&mux_lock);
206         mux = device_connection_find_match(dev, "mode-switch", (void *)desc,
207                                            typec_mux_match);
208         if (!IS_ERR_OR_NULL(mux)) {
209                 WARN_ON(!try_module_get(mux->dev->driver->owner));
210                 get_device(mux->dev);
211         }
212         mutex_unlock(&mux_lock);
213
214         return mux;
215 }
216 EXPORT_SYMBOL_GPL(typec_mux_get);
217
218 /**
219  * typec_mux_put - Release handle to a Multiplexer
220  * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
221  *
222  * Decrements reference count for @mux.
223  */
224 void typec_mux_put(struct typec_mux *mux)
225 {
226         if (!IS_ERR_OR_NULL(mux)) {
227                 module_put(mux->dev->driver->owner);
228                 put_device(mux->dev);
229         }
230 }
231 EXPORT_SYMBOL_GPL(typec_mux_put);
232
233 /**
234  * typec_mux_register - Register Multiplexer routing USB Type-C pins
235  * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
236  *
237  * USB Type-C connectors can be used for alternate modes of operation besides
238  * USB when Accessory/Alternate Modes are supported. With some of those modes,
239  * the pins on the connector need to be reconfigured. This function registers
240  * multiplexer switches routing the pins on the connector.
241  */
242 int typec_mux_register(struct typec_mux *mux)
243 {
244         mutex_lock(&mux_lock);
245         list_add_tail(&mux->entry, &mux_list);
246         mutex_unlock(&mux_lock);
247
248         return 0;
249 }
250 EXPORT_SYMBOL_GPL(typec_mux_register);
251
252 /**
253  * typec_mux_unregister - Unregister Multiplexer Switch
254  * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
255  *
256  * Unregister mux that was registered with typec_mux_register().
257  */
258 void typec_mux_unregister(struct typec_mux *mux)
259 {
260         mutex_lock(&mux_lock);
261         list_del(&mux->entry);
262         mutex_unlock(&mux_lock);
263 }
264 EXPORT_SYMBOL_GPL(typec_mux_unregister);
This page took 0.049818 seconds and 4 git commands to generate.